<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>使用 koa2 开发服务端 | 小熊的技术文档</title>
    <meta name="generator" content="VuePress 1.8.2">
    <link rel="icon" href="/fav.ico">
    <link rel="stylesheet" href="/css/katex.min.css">
    <link rel="stylesheet" href="/css/github-markdown.min.css">
    <meta name="description" content="衣带渐宽终不悔，为伊消得人憔悴">
    
    <link rel="preload" href="/assets/css/0.styles.e6449353.css" as="style"><link rel="preload" href="/assets/js/app.9e067b79.js" as="script"><link rel="preload" href="/assets/js/2.983eb755.js" as="script"><link rel="preload" href="/assets/js/11.31600f80.js" as="script"><link rel="prefetch" href="/assets/js/10.79548333.js"><link rel="prefetch" href="/assets/js/12.62bd3528.js"><link rel="prefetch" href="/assets/js/13.de8b1ace.js"><link rel="prefetch" href="/assets/js/14.f5db1e75.js"><link rel="prefetch" href="/assets/js/15.bf827d4d.js"><link rel="prefetch" href="/assets/js/16.cc9a1a73.js"><link rel="prefetch" href="/assets/js/17.2cfaefeb.js"><link rel="prefetch" href="/assets/js/18.3af7782b.js"><link rel="prefetch" href="/assets/js/19.639f7e7b.js"><link rel="prefetch" href="/assets/js/20.dc862f59.js"><link rel="prefetch" href="/assets/js/21.916e1685.js"><link rel="prefetch" href="/assets/js/22.d7a955f4.js"><link rel="prefetch" href="/assets/js/23.078391ef.js"><link rel="prefetch" href="/assets/js/24.bb460a5a.js"><link rel="prefetch" href="/assets/js/25.d69f2326.js"><link rel="prefetch" href="/assets/js/26.6da7ea48.js"><link rel="prefetch" href="/assets/js/27.82e23d91.js"><link rel="prefetch" href="/assets/js/28.9073bbec.js"><link rel="prefetch" href="/assets/js/29.639259a4.js"><link rel="prefetch" href="/assets/js/3.e594e5b2.js"><link rel="prefetch" href="/assets/js/30.b49c622d.js"><link rel="prefetch" href="/assets/js/31.92f6c8b3.js"><link rel="prefetch" href="/assets/js/32.42419088.js"><link rel="prefetch" href="/assets/js/33.c82e2ab8.js"><link rel="prefetch" href="/assets/js/34.381de37e.js"><link rel="prefetch" href="/assets/js/35.5e86d478.js"><link rel="prefetch" href="/assets/js/36.31f218ce.js"><link rel="prefetch" href="/assets/js/37.0d287b3f.js"><link rel="prefetch" href="/assets/js/38.1324cf44.js"><link rel="prefetch" href="/assets/js/39.de6bdb51.js"><link rel="prefetch" href="/assets/js/4.440c4dd9.js"><link rel="prefetch" href="/assets/js/40.a22c9c27.js"><link rel="prefetch" href="/assets/js/41.4637d617.js"><link rel="prefetch" href="/assets/js/42.db815e2b.js"><link rel="prefetch" href="/assets/js/43.f0955a92.js"><link rel="prefetch" href="/assets/js/44.7d5faddf.js"><link rel="prefetch" href="/assets/js/45.a88ffc33.js"><link rel="prefetch" href="/assets/js/46.883caa71.js"><link rel="prefetch" href="/assets/js/47.6f2cfd60.js"><link rel="prefetch" href="/assets/js/48.ea25654e.js"><link rel="prefetch" href="/assets/js/49.f38c23a0.js"><link rel="prefetch" href="/assets/js/5.e8844f36.js"><link rel="prefetch" href="/assets/js/50.e03dd8e9.js"><link rel="prefetch" href="/assets/js/51.f6160d52.js"><link rel="prefetch" href="/assets/js/52.4daa8322.js"><link rel="prefetch" href="/assets/js/53.b30992e9.js"><link rel="prefetch" href="/assets/js/54.209f17e1.js"><link rel="prefetch" href="/assets/js/55.4f1dd95b.js"><link rel="prefetch" href="/assets/js/56.147ea3e8.js"><link rel="prefetch" href="/assets/js/57.5823e0e2.js"><link rel="prefetch" href="/assets/js/58.772320f9.js"><link rel="prefetch" href="/assets/js/59.5ab55a80.js"><link rel="prefetch" href="/assets/js/6.54e1cc95.js"><link rel="prefetch" href="/assets/js/60.b47b75bb.js"><link rel="prefetch" href="/assets/js/61.0cd5a012.js"><link rel="prefetch" href="/assets/js/62.35eb5453.js"><link rel="prefetch" href="/assets/js/63.d31f2118.js"><link rel="prefetch" href="/assets/js/64.03d35d7c.js"><link rel="prefetch" href="/assets/js/65.8bdc633f.js"><link rel="prefetch" href="/assets/js/66.dbbe8867.js"><link rel="prefetch" href="/assets/js/67.228613b5.js"><link rel="prefetch" href="/assets/js/68.d10a2687.js"><link rel="prefetch" href="/assets/js/69.7903847f.js"><link rel="prefetch" href="/assets/js/7.f5ab00eb.js"><link rel="prefetch" href="/assets/js/70.11ee4e27.js"><link rel="prefetch" href="/assets/js/8.95b39934.js"><link rel="prefetch" href="/assets/js/9.26cfd48c.js">
    <link rel="stylesheet" href="/assets/css/0.styles.e6449353.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/" class="home-link router-link-active"><!----> <span class="site-name">小熊的技术文档</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/front-end/" class="nav-link">
  🎨前端
</a></div><div class="nav-item"><a href="/back-end/" class="nav-link router-link-active">
  💻后端
</a></div><div class="nav-item"><a href="/practice/" class="nav-link">
  🚀实战
</a></div><div class="nav-item"><a href="/office/" class="nav-link">
  🏢办公
</a></div><div class="nav-item"><a href="/general/" class="nav-link">
  🍓通用
</a></div><div class="nav-item"><a href="/paper/" class="nav-link">
  🐸论文
</a></div><div class="nav-item"><a href="/general/fast.html" class="nav-link">
  ⚡快速笔记
</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="🦉近期重点" class="dropdown-title"><span class="title">🦉近期重点</span> <span class="arrow down"></span></button> <button type="button" aria-label="🦉近期重点" class="mobile-dropdown-title"><span class="title">🦉近期重点</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/back-end/python.html" class="nav-link">
  🐇python常用模块
</a></li><li class="dropdown-item"><!----> <a href="/practice/zrender.html" class="nav-link">
  🌹zrender源码解析
</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="⭐️资源" class="dropdown-title"><span class="title">⭐️资源</span> <span class="arrow down"></span></button> <button type="button" aria-label="⭐️资源" class="mobile-dropdown-title"><span class="title">⭐️资源</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="https://www.birdiesearch.com/" target="_blank" rel="noopener noreferrer" class="nav-link external">
  小鸟搜索
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://salttiger.com/" target="_blank" rel="noopener noreferrer" class="nav-link external">
  每天一本编程书
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://gitee.com/docmirror/dev-sidecar" target="_blank" rel="noopener noreferrer" class="nav-link external">
  开发者边车
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div> <!----></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/front-end/" class="nav-link">
  🎨前端
</a></div><div class="nav-item"><a href="/back-end/" class="nav-link router-link-active">
  💻后端
</a></div><div class="nav-item"><a href="/practice/" class="nav-link">
  🚀实战
</a></div><div class="nav-item"><a href="/office/" class="nav-link">
  🏢办公
</a></div><div class="nav-item"><a href="/general/" class="nav-link">
  🍓通用
</a></div><div class="nav-item"><a href="/paper/" class="nav-link">
  🐸论文
</a></div><div class="nav-item"><a href="/general/fast.html" class="nav-link">
  ⚡快速笔记
</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="🦉近期重点" class="dropdown-title"><span class="title">🦉近期重点</span> <span class="arrow down"></span></button> <button type="button" aria-label="🦉近期重点" class="mobile-dropdown-title"><span class="title">🦉近期重点</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/back-end/python.html" class="nav-link">
  🐇python常用模块
</a></li><li class="dropdown-item"><!----> <a href="/practice/zrender.html" class="nav-link">
  🌹zrender源码解析
</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="⭐️资源" class="dropdown-title"><span class="title">⭐️资源</span> <span class="arrow down"></span></button> <button type="button" aria-label="⭐️资源" class="mobile-dropdown-title"><span class="title">⭐️资源</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="https://www.birdiesearch.com/" target="_blank" rel="noopener noreferrer" class="nav-link external">
  小鸟搜索
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://salttiger.com/" target="_blank" rel="noopener noreferrer" class="nav-link external">
  每天一本编程书
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li><li class="dropdown-item"><!----> <a href="https://gitee.com/docmirror/dev-sidecar" target="_blank" rel="noopener noreferrer" class="nav-link external">
  开发者边车
  <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div></div> <!----></nav>  <ul class="sidebar-links"><li><a href="/back-end/daily.html" class="sidebar-link">每日冷知识</a></li><li><a href="/back-end/python.html" class="sidebar-link">Python 常用操作笔记</a></li><li><a href="/back-end/python-projects.html" class="sidebar-link">小python项目</a></li><li><a href="/back-end/koa2.html" aria-current="page" class="active sidebar-link">使用 koa2 开发服务端</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/back-end/koa2.html#初始化与基本概念-init" class="sidebar-link">初始化与基本概念(init)</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/back-end/koa2.html#代码修改后自动重启" class="sidebar-link">代码修改后自动重启</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#最简单服务器" class="sidebar-link">最简单服务器</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#使用中间件" class="sidebar-link">使用中间件</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#洋葱模型" class="sidebar-link">洋葱模型</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#async-和-await-功能" class="sidebar-link">async 和 await 功能</a></li></ul></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#路由系统-router" class="sidebar-link">路由系统(Router)</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/back-end/koa2.html#不使用路由的-koa2" class="sidebar-link">不使用路由的 koa2</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#koa-router-用法" class="sidebar-link">Koa-router 用法</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#路由拆分" class="sidebar-link">路由拆分</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#自动加载所有路由模块" class="sidebar-link">自动加载所有路由模块</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#增加初始化管理器-initmanager" class="sidebar-link">增加初始化管理器(InitManager)</a></li></ul></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#异常处理-httpexception" class="sidebar-link">异常处理(HttpException)</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/back-end/koa2.html#koa2-获取-4-种类型的参数" class="sidebar-link">koa2 获取 4 种类型的参数</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#koa2-异常处理" class="sidebar-link">koa2 异常处理</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#增加全局异常处理-catcherror" class="sidebar-link">增加全局异常处理(catchError)</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#使用变量挂载方式完善全局异常处理" class="sidebar-link">使用变量挂载方式完善全局异常处理</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#使用类的方式改进异常生成" class="sidebar-link">使用类的方式改进异常生成</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#定义多种异常类方便使用" class="sidebar-link">定义多种异常类方便使用</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#区分生产环境和开发环境" class="sidebar-link">区分生产环境和开发环境</a></li></ul></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#参数校验-validator" class="sidebar-link">参数校验(Validator)</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/back-end/koa2.html#使用校验器" class="sidebar-link">使用校验器</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#使用校验器获取参数" class="sidebar-link">使用校验器获取参数</a></li></ul></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#数据持久化" class="sidebar-link">数据持久化</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/back-end/koa2.html#安装-mysql-与-navicat" class="sidebar-link">安装 mysql 与 navicat</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#使用-sequelize-创建-user-表" class="sidebar-link">使用 Sequelize 创建 User 表</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#user-表注册功能验证相关信息" class="sidebar-link">User 表注册功能验证相关信息</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#增加-email-规则校验" class="sidebar-link">增加 email 规则校验</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#user-表保存信息到数据库" class="sidebar-link">User 表保存信息到数据库</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#在模型中对-password-密码加密" class="sidebar-link">在模型中对 password 密码加密</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#处理操作成功-success-的返回情况" class="sidebar-link">处理操作成功(Success)的返回情况</a></li></ul></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#令牌发放" class="sidebar-link">令牌发放</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/back-end/koa2.html#定义-type-相关枚举类型-enum" class="sidebar-link">定义 Type 相关枚举类型(Enum)</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#验证用户邮箱和密码是否正确" class="sidebar-link">验证用户邮箱和密码是否正确</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#生成-jwt-令牌-token" class="sidebar-link">生成 jwt 令牌(token)</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#jwt-令牌校验与取值" class="sidebar-link">jwt 令牌校验与取值</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#前端获取令牌-token" class="sidebar-link">前端获取令牌 token</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#权限设计" class="sidebar-link">权限设计</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#业务逻辑编写位置" class="sidebar-link">业务逻辑编写位置</a></li></ul></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#数据库-database-操作" class="sidebar-link">数据库(database)操作</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/back-end/koa2.html#实体表和业务表概念" class="sidebar-link">实体表和业务表概念</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#数据排序-order-删除-destory-高级查找-find" class="sidebar-link">数据排序(order), 删除(destory), 高级查找(find)</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#模型序列化" class="sidebar-link">模型序列化</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#数据库事务-transaction" class="sidebar-link">数据库事务(Transaction)</a></li></ul></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#其他需要注意的问题" class="sidebar-link">其他需要注意的问题</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/back-end/koa2.html#常见问题" class="sidebar-link">常见问题</a></li><li class="sidebar-sub-header"><a href="/back-end/koa2.html#增加跨域中间件-cross-domain" class="sidebar-link">增加跨域中间件(cross-domain)</a></li></ul></li></ul></li><li><a href="/back-end/numpy.html" class="sidebar-link">从python到Numpy</a></li><li><a href="/back-end/linux.html" class="sidebar-link">linux核心技能</a></li><li><a href="/back-end/docker.html" class="sidebar-link">Docker实战</a></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><h1 id="使用-koa2-开发服务端"><a href="#使用-koa2-开发服务端" class="header-anchor">#</a> 使用 koa2 开发服务端</h1> <blockquote><p>Koa -- 基于 Node.js 平台的下一代 web 开发框架, 可查阅<a href="https://koa.bootcss.com/" target="_blank" rel="noopener noreferrer">Koa2 文档<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>, 项目参考了 <a href="https://github.com/TaleLin/lin-cms-koa" target="_blank" rel="noopener noreferrer">Lin-CMS-Koa<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>, 并且也大量使用了<a href="http://www.imooc.com" target="_blank" rel="noopener noreferrer">慕课网<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>课程<a href="https://coding.imooc.com/class/342.html" target="_blank" rel="noopener noreferrer">Koa2 服务端开发<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>, 感谢<strong>林间有风</strong>团队, 特别是慕课网<a href="http://www.imooc.com/t/4294850" target="_blank" rel="noopener noreferrer">七月<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>老师, 欢迎大家支持正版.</p></blockquote> <h2 id="初始化与基本概念-init"><a href="#初始化与基本概念-init" class="header-anchor">#</a> 初始化与基本概念(init)</h2> <p>依赖环境:</p> <ul><li>koa2 版本为<code>2.7.0</code></li> <li>node.js 版本为<code>12.14.0</code></li></ul> <p>依赖的所有模块:</p> <div class="language-json extra-class"><pre class="language-json"><code><span class="token property">&quot;dependencies&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token property">&quot;axios&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^0.18.0&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;basic-auth&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^2.0.1&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;bcryptjs&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^2.4.3&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;jsonwebtoken&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^8.4.0&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;koa&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^2.7.0&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;koa-bodyparser&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^4.2.1&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;koa-router&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^7.4.0&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;koa-static&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^5.0.0&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;lodash&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^4.17.11&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;module-alias&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^2.2.0&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;mysql2&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^1.6.5&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;npm-check&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^5.9.0&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;require-directory&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^2.1.1&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;sequelize&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^5.6.1&quot;</span><span class="token punctuation">,</span>
    <span class="token property">&quot;validator&quot;</span><span class="token operator">:</span> <span class="token string">&quot;^10.11.0&quot;</span>
  <span class="token punctuation">}</span>
</code></pre></div><p>使用<code>npm init -y</code>后, 使用<code>npm install</code>安装所有模块.</p> <h3 id="代码修改后自动重启"><a href="#代码修改后自动重启" class="header-anchor">#</a> 代码修改后自动重启</h3> <p>安装<code>nodemon</code>, 建议全局安装, 使用<code>npm install nodemon -g</code>, 如果不是全局安装, 则可以使用<code>npx nodemon</code>运行或者编写<code>scripts</code>也行.<br>
然后在命令行中输入<code>nodemon app.js</code>即可运行服务器, 并且监控文件修改情况, 一有文件修改则自动重启服务器.</p> <p>在 vscode 中, 可以实现既能自动重启, 还能断点调试, 在 vscode 中点击[debug]菜单, 然后增加配置, 选择<code>nodemon安装程序</code><br> <img src="https://s1.ax1x.com/2020/04/25/JsNWzF.png" alt="vscode中nodemon配置"><br>
配置之后的<code>.vscode\launch.json</code>文件如下所示:</p> <div class="language-json extra-class"><pre class="language-json"><code><span class="token punctuation">{</span>
  <span class="token property">&quot;version&quot;</span><span class="token operator">:</span> <span class="token string">&quot;0.2.0&quot;</span><span class="token punctuation">,</span>
  <span class="token property">&quot;configurations&quot;</span><span class="token operator">:</span> <span class="token punctuation">[</span>
    <span class="token punctuation">{</span>
      <span class="token property">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;node&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;request&quot;</span><span class="token operator">:</span> <span class="token string">&quot;launch&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;name&quot;</span><span class="token operator">:</span> <span class="token string">&quot;nodemon运行&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;runtimeExecutable&quot;</span><span class="token operator">:</span> <span class="token string">&quot;nodemon&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;program&quot;</span><span class="token operator">:</span> <span class="token string">&quot;${workspaceFolder}/app.js&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;restart&quot;</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
      <span class="token property">&quot;console&quot;</span><span class="token operator">:</span> <span class="token string">&quot;integratedTerminal&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;internalConsoleOptions&quot;</span><span class="token operator">:</span> <span class="token string">&quot;neverOpen&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;skipFiles&quot;</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">&quot;&lt;node_internals&gt;/**&quot;</span><span class="token punctuation">]</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      <span class="token property">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;node&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;request&quot;</span><span class="token operator">:</span> <span class="token string">&quot;launch&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;name&quot;</span><span class="token operator">:</span> <span class="token string">&quot;启动程序&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;skipFiles&quot;</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">&quot;&lt;node_internals&gt;/**&quot;</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token property">&quot;program&quot;</span><span class="token operator">:</span> <span class="token string">&quot;${workspaceFolder}\\app.js&quot;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">{</span>
      <span class="token property">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;node&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;request&quot;</span><span class="token operator">:</span> <span class="token string">&quot;launch&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;name&quot;</span><span class="token operator">:</span> <span class="token string">&quot;运行当前文件&quot;</span><span class="token punctuation">,</span>
      <span class="token property">&quot;skipFiles&quot;</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">&quot;&lt;node_internals&gt;/**&quot;</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
      <span class="token property">&quot;program&quot;</span><span class="token operator">:</span> <span class="token string">&quot;${file}&quot;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">]</span>
<span class="token punctuation">}</span>
</code></pre></div><div class="custom-block tip"><p class="custom-block-title">小技巧</p> <p>在 debug 时, 选中一段代码, 点击右键选中&quot;调试求值&quot;,就可以在[调试控制台]看到结果了.<br> <img src="https://s1.ax1x.com/2020/04/25/Js0tgK.png" alt="调试求值"></p></div> <h3 id="最简单服务器"><a href="#最简单服务器" class="header-anchor">#</a> 最简单服务器</h3> <p>koa2 服务器运行很简单, 下面代码运行最基本的 koa2 服务器, 在终端输入<code>node app.js</code>即可运行.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'启动成功'</span><span class="token punctuation">)</span>
</code></pre></div><h3 id="使用中间件"><a href="#使用中间件" class="header-anchor">#</a> 使用中间件</h3> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment">// 实现中间件, 每次访问都会调用打印</span>
<span class="token comment">// 在第一个中间件中调用第2个中间件</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Hello World'</span><span class="token punctuation">)</span>
  <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 定义第2个中间件</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Hello World2'</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 监听3000端口</span>
app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在3000端口服务端启动成功!'</span><span class="token punctuation">)</span>
</code></pre></div><h3 id="洋葱模型"><a href="#洋葱模型" class="header-anchor">#</a> 洋葱模型</h3> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment">// 第1个中间件, next调用使用await</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'1'</span><span class="token punctuation">)</span>
  <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 调用下个中间件</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'2'</span><span class="token punctuation">)</span> <span class="token comment">// 下个中间件运行完毕后才会执行这个</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 第2个中间件, next调用使用await</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span>
  <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token number">4</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在3000端口服务端启动成功!'</span><span class="token punctuation">)</span>
</code></pre></div><p>访问端口后, 代码运行结果是打印&quot;1,3,4,2&quot;<br>
koa2 使用的是<strong>洋葱模型</strong>, 跟本代码相关的图例如下:<br> <img src="https://s1.ax1x.com/2020/04/25/Js9myn.png" alt="洋葱模型.png"><br>
详细的洋葱模型是下图这样的:<br> <img src="https://s1.ax1x.com/2020/04/25/Js98W4.png" alt="洋葱模型完整版.png"><br>
洋葱模型是以<code>await next()</code>为分界线, 例如要实现计时功能, 那么在 next 函数之后的代码能保证后续所有代码都已经执行完毕了.</p> <h3 id="async-和-await-功能"><a href="#async-和-await-功能" class="header-anchor">#</a> async 和 await 功能</h3> <p>中间件必须使用 async/await 关键词进行修饰, C#首次引用 async/await, 号称异步终极解决方案, 主要作用如下:</p> <ul><li>求值</li> <li>阻塞线程, 等待返回结果</li></ul> <div class="custom-block tip"><p class="custom-block-title">小技巧</p> <p>虽然 async/await 会阻塞线程, 但是它不会影响其他线程执行, 所以还是能保证代码高效.</p></div> <p>保证洋葱模型的例子</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment">// 第1个中间件</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token comment">// 在next之后,能保证后续代码已全部执行,才能拿到ctx.r</span>
  <span class="token keyword">const</span> r <span class="token operator">=</span> ctx<span class="token punctuation">.</span>r
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>r<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 第2个中间件</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> axios <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'axios'</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> res <span class="token operator">=</span> <span class="token keyword">await</span> axios<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'https://api.uomg.com/api/rand.qinghua'</span><span class="token punctuation">)</span>
  <span class="token comment">// 将返回值挂载到ctx.r上</span>
  ctx<span class="token punctuation">.</span>r <span class="token operator">=</span> res
  <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 监听3000端口</span>
app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在3000端口服务端启动成功!'</span><span class="token punctuation">)</span>
</code></pre></div><h2 id="路由系统-router"><a href="#路由系统-router" class="header-anchor">#</a> 路由系统(Router)</h2> <h3 id="不使用路由的-koa2"><a href="#不使用路由的-koa2" class="header-anchor">#</a> 不使用路由的 koa2</h3> <p>如果不适用路由,则需要各种判断 ctx.path 和 ctx.method, api 列表一多,则特别不方便维护</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">//查阅官网文档, 找到Request的路径和访问方式</span>
  <span class="token comment">// 为了简化操作, 官网文档说下面访问器和 Request 别名等效,所以可以直接在ctx上取</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>path<span class="token punctuation">)</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>method<span class="token punctuation">)</span>
  <span class="token comment">// 下面其实是完成路由功能, 如果api多则需要很多判断</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>path <span class="token operator">===</span> <span class="token string">'/api/v1/login'</span> <span class="token operator">&amp;&amp;</span> ctx<span class="token punctuation">.</span>method <span class="token operator">===</span> <span class="token string">'GET'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 返回值必须要放到ctx.body中才能显示, 不能直接return, 传入js对象可直接转换json</span>
    ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> key<span class="token operator">:</span> <span class="token string">'登录失败'</span> <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 监听3000端口</span>
app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在3000端口服务端启动成功!'</span><span class="token punctuation">)</span>
</code></pre></div><h3 id="koa-router-用法"><a href="#koa-router-用法" class="header-anchor">#</a> Koa-router 用法</h3> <p>koa-router 是基于 koa 路由的中间件, 可在<a href="https://github.com/ZijianHe/koa-router" target="_blank" rel="noopener noreferrer">koa-router 文档<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>中进行查阅, 基本用法有 3 步:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">var</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">var</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>

<span class="token keyword">var</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment">// 第1步 实例化router</span>
<span class="token keyword">var</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment">// 第2步 编写路由函数</span>
router<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// ctx.router available</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 第3步 在app中使用中间件</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>router<span class="token punctuation">.</span><span class="token function">routes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>router<span class="token punctuation">.</span><span class="token function">allowedMethods</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</code></pre></div><p>使用 koa-router 将原有代码进行改造</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 实例化router</span>

<span class="token comment">// 编写路由</span>
router<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/api/v1/login'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> key<span class="token operator">:</span> <span class="token string">'登录失败'</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 注册</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>router<span class="token punctuation">.</span><span class="token function">routes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在3000端口服务端启动成功!'</span><span class="token punctuation">)</span>
</code></pre></div><h3 id="路由拆分"><a href="#路由拆分" class="header-anchor">#</a> 路由拆分</h3> <p>将原本在 app.js 中的路由放到 api 文件中,整体代码结构如下所示:</p> <div class="language-bash extra-class"><pre class="language-bash"><code>my-koa2
 ├── api
 │   ├── v1
 │   │   ├── deploy.js
 │   │   └── user.js
 │   └── v2
 └── app.js
</code></pre></div><p>app.js 中的代码</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token comment">// 把路由分布到不同的文件中去</span>
<span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./api/v1/user'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> deploy <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./api/v1/deploy'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment">// 统一在app.js中注册路由</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span><span class="token function">routes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>deploy<span class="token punctuation">.</span><span class="token function">routes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在3000端口服务端启动成功!'</span><span class="token punctuation">)</span>
</code></pre></div><p>在每个模块代码中导入 <code>koa-router</code>, 实例化路由并编写路由代码后再并将其导出, <code>user.js</code> 中代码如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>
<span class="token comment">// 实例化router</span>
<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment">// 编写路由</span>
router<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/api/v1/login'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> key<span class="token operator">:</span> <span class="token string">'登录失败'</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 导出路由</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><h3 id="自动加载所有路由模块"><a href="#自动加载所有路由模块" class="header-anchor">#</a> 自动加载所有路由模块</h3> <p>使用<code>require-directory</code>可以自动加载目录下所有模块, 查看<a href="https://www.npmjs.com/package/require-directory" target="_blank" rel="noopener noreferrer">require-directory 文档<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>, app.js 中改写为下面的代码样式</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> requireDirectory <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'require-directory'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment">// requireDirectory的第3个参数传入一个对象</span>
<span class="token comment">// 对象的visit属性可以传入一个回调函数,</span>
<span class="token comment">// 就是对每个模块进行导入成功后的操作</span>
<span class="token comment">// 需要确保路由模块都是采用default形式进行导出, 即module.exports = router</span>
<span class="token function">requireDirectory</span><span class="token punctuation">(</span>module<span class="token punctuation">,</span> <span class="token string">'./api'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token function-variable function">visit</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token parameter">obj</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// 如果是router对象,则在app上进行注册</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>obj <span class="token keyword">instanceof</span> <span class="token class-name">Router</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>obj<span class="token punctuation">.</span><span class="token function">routes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Server Start in port 3000!'</span><span class="token punctuation">)</span>
</code></pre></div><div class="custom-block warning"><p class="custom-block-title">注意</p> <p>需要确保路由模块都是采用 default 形式进行导出, 即<code>module.exports = router</code>.</p></div> <h3 id="增加初始化管理器-initmanager"><a href="#增加初始化管理器-initmanager" class="header-anchor">#</a> 增加初始化管理器(InitManager)</h3> <div class="custom-block tip"><p class="custom-block-title">小技巧</p> <p><code>process.cwd()</code>能显示当前项目的绝对路径, 例如<code>&quot;D:\js_projects\my-koa2&quot;</code></p></div> <p>重构后的项目结构如下所示:</p> <div class="language-bash extra-class"><pre class="language-bash"><code>my-koa2
 ├── app
 │   └── api
 │       ├── v1
 │       │   ├── deploy.js
 │       │   └── user.js
 │       └── v2
 ├── app.js
 └── core
     └── init.js
</code></pre></div><p>增加初始化管理器类<code>InitManager</code>位于<code>init.js</code>中</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> requireDirectory <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'require-directory'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>

<span class="token keyword">class</span> <span class="token class-name">InitManager</span> <span class="token punctuation">{</span>
  <span class="token comment">/**
   * 入口方法
   * @param {Koa} app
   */</span>
  <span class="token keyword">static</span> <span class="token function">initCore</span><span class="token punctuation">(</span><span class="token parameter">app</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    InitManager<span class="token punctuation">.</span>app <span class="token operator">=</span> app
    <span class="token comment">// 使用静态方法需要用`类名.方法()`进行调用</span>
    InitManager<span class="token punctuation">.</span><span class="token function">initLoadRouters</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token comment">/**
   *  导入路由方法
   */</span>
  <span class="token keyword">static</span> <span class="token function">initLoadRouters</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 使用绝对路径引用api, 这样无论怎么修改init.js文件位置都没关系,</span>
    <span class="token comment">// 前提是api位置不能有变化</span>
    <span class="token keyword">const</span> apiDir <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>process<span class="token punctuation">.</span><span class="token function">cwd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/app/api</span><span class="token template-punctuation string">`</span></span>
    <span class="token function">requireDirectory</span><span class="token punctuation">(</span>module<span class="token punctuation">,</span> apiDir<span class="token punctuation">,</span> <span class="token punctuation">{</span>
      visit<span class="token operator">:</span> whenLoadModule<span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token comment">// 当加载路由模块后的方法</span>
    <span class="token keyword">function</span> <span class="token function">whenLoadModule</span><span class="token punctuation">(</span><span class="token parameter">obj</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// 如果是router对象,则在app上进行注册</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>obj <span class="token keyword">instanceof</span> <span class="token class-name">Router</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        InitManager<span class="token punctuation">.</span>app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>obj<span class="token punctuation">.</span><span class="token function">routes</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> InitManager
</code></pre></div><p><code>app.js</code>可以轻装上阵:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> InitManager <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./core/init'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment">// 将app传入到初始化类中</span>
InitManager<span class="token punctuation">.</span><span class="token function">initCore</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Server Start in port 3000!'</span><span class="token punctuation">)</span>
</code></pre></div><div class="custom-block warning"><p class="custom-block-title">注意</p> <p>由于<code>process.cwd()</code>能显示当前项目的绝对路径, 通过与绝对路径修改 api 文件夹位置, 这样无论怎么修改<code>init.js</code>文件位置都没关系,但是<strong>必须保证</strong><code>api</code>文件夹的位置始终位于项目工程根目录的<code>./app/api</code>中.</p></div> <h2 id="异常处理-httpexception"><a href="#异常处理-httpexception" class="header-anchor">#</a> 异常处理(HttpException)</h2> <h3 id="koa2-获取-4-种类型的参数"><a href="#koa2-获取-4-种类型的参数" class="header-anchor">#</a> koa2 获取 4 种类型的参数</h3> <p>使用 post 传递参数, 一般使用 postman 进行发送, 传递 json 格式的参数, 在<code>postman</code>的[body]栏中应该选择<code>raw</code>, 格式为<code>JSONapplication/json</code><br> <img src="https://s1.ax1x.com/2020/04/25/JyC1Zq.png" alt="postman传递json参数"></p> <p>一般的参数传递有 4 种方式:</p> <ul><li>在 url 中包含参数, 例如<code>http://t.cn/3</code>表示第 3 页等</li> <li>url 中的?传参,<code>http://t.cn/test/?param=weiwei</code></li> <li>body 传参, 在 body 中使用<code>application/json</code>格式</li> <li>heads 传参, 在 head 中以键值对的方式进行传参</li></ul> <p>一个包括上述所有参数的例如如下所示:<br> <img src="https://s1.ax1x.com/2020/04/25/JyizGT.png" alt="postman模拟4种传参方式"></p> <p>为了获取 body 中的参数, 需要安装<code>koa-bodyparser</code>中间件, 它会自动把 body 的参数挂载到 request 对象的 body 属性中去, 修改后的<code>app.js</code>代码如下所示:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> InitManager <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./core/init'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> parser <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-bodyparser'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

<span class="token comment">// 使用bodyParser, 以便解析body中的参数, 需要在初始化之前使用</span>
<span class="token comment">// 会自动把body的参数挂载到request对象的body属性中去</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token function">parser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>

<span class="token comment">// 将app传入到初始化类中</span>
InitManager<span class="token punctuation">.</span><span class="token function">initCore</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Server Start in port 3000!'</span><span class="token punctuation">)</span>
</code></pre></div><p>我们在<code>user.js</code>尝试获取参数, 写法如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 实例化router</span>

router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/v1/:id/test'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> path <span class="token operator">=</span> ctx<span class="token punctuation">.</span>params <span class="token comment">// 获取到{id:&quot;1&quot;}</span>
  <span class="token keyword">const</span> query <span class="token operator">=</span> ctx<span class="token punctuation">.</span>request<span class="token punctuation">.</span>query <span class="token comment">// 获取到{param:&quot;weiwei&quot;}</span>
  <span class="token keyword">const</span> headers <span class="token operator">=</span> ctx<span class="token punctuation">.</span>request<span class="token punctuation">.</span>header <span class="token comment">// 对象包含很多属性, 其中token属性为12345678</span>
  <span class="token comment">// 在使用路由之前, 使用了koa-bodyparser中间件</span>
  <span class="token comment">// 才能在request的body属性中获取值</span>
  <span class="token keyword">const</span> body <span class="token operator">=</span> ctx<span class="token punctuation">.</span>request<span class="token punctuation">.</span>body <span class="token comment">// 获取到{test: 2}</span>

  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> key<span class="token operator">:</span> <span class="token string">'获取参数成功'</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><div class="custom-block warning"><p class="custom-block-title">注意</p> <p>必须保证在使用路由<strong>之前</strong>, 在<code>app</code>中使用挂载 koa-bodyparser 中间件, 否则路由是无法获取到<code>body</code>属性的.</p></div> <h3 id="koa2-异常处理"><a href="#koa2-异常处理" class="header-anchor">#</a> koa2 异常处理</h3> <p>函数中出现异常, 有 2 种处理方法, 一种是<code>return false</code>或者<code>return null</code>, 另外一种是<code>throw new Error</code>, 一般来说, 第二种方法更好一些, 因为返回 null 会丢失异常, 我们需要捕捉异常告诉开发者.<br>
最好是定义全局异常处理, 在所有函数调用的最顶部, 能够监听到所有的异常, 方便操作.<br>
异步操作时不太好捕捉异常的, 一般操作是在所有 promise 返回的对象都加上 async/await, 然后包裹 try/catch 即可, 示例代码如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 在另一个函数中利用async/await关键字使用try/catch捕获异常</span>
<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">func2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token function">func3</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'error'</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// 自己创建一个promise</span>
<span class="token keyword">function</span> <span class="token function">func3</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">resolve<span class="token punctuation">,</span> reject</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> r <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
      <span class="token comment">// 随机抛出异常</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>r <span class="token operator">&gt;</span> <span class="token number">0.5</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token function">reject</span><span class="token punctuation">(</span><span class="token string">'error'</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
        <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token string">'success'</span><span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>

<span class="token function">func2</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 调用函数执行</span>
</code></pre></div><div class="custom-block warning"><p class="custom-block-title">注意</p> <p>javascript 中<code>1/0</code>不会报错, 会返回一个值<code>Infinity</code>的值, 表示无限大.<br>
如果 promise 的异常没有处理, 例如没有用 await 来接收, 则会报&quot;UnhandledPromiseRejectionWarning&quot;错误</p></div> <h3 id="增加全局异常处理-catcherror"><a href="#增加全局异常处理-catcherror" class="header-anchor">#</a> 增加全局异常处理(catchError)</h3> <p>主要思想是增加一个中间件, 把所有函数都放到中间件的 try/catch 中去, 如果出现问题则修改 body.<br>
新增中间件<code>middlewares</code>文件夹, 编写<code>exception.js</code>文件</p> <div class="language-bash extra-class"><pre class="language-bash"><code>my-koa2
 ├── app
 │   └── api
 │       ├── v1
 │       │   ├── deploy.js
 │       │   └── user.js
 │       └── v2
 ├── app.js
 ├── core
 │   └── init.js
 └── middlewares
     └── exception.js
</code></pre></div><p><code>exception.js</code>文件内容如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 自己编写全局异常处理, 有点面向切面编程的感觉</span>
<span class="token keyword">const</span> <span class="token function-variable function">catchError</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> message<span class="token operator">:</span> <span class="token string">'服务器出现问题'</span> <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> catchError
</code></pre></div><p>再在 app.js 中增加处理调用中间件代码.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Koa <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> InitManager <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./core/init'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> parser <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-bodyparser'</span><span class="token punctuation">)</span>
<span class="token comment">// 引入全局异常处理</span>
<span class="token keyword">const</span> catchError <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./middlewares/exception'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token comment">// 首先进行全局异常处理</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>catchError<span class="token punctuation">)</span>

<span class="token comment">// 解析body参数, 调用初始化类</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token function">parser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
InitManager<span class="token punctuation">.</span><span class="token function">initCore</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Server Start in port 3000!'</span><span class="token punctuation">)</span>
</code></pre></div><h3 id="使用变量挂载方式完善全局异常处理"><a href="#使用变量挂载方式完善全局异常处理" class="header-anchor">#</a> 使用变量挂载方式完善全局异常处理</h3> <p>返回的异常一般有四种信息:</p> <ul><li>status: 整型, Http 状态码, 例如 200, 301, 400, 500 等</li> <li>message: 字符串型, 给客户端的消息信息</li> <li>errorCode: 整型, 自定义的详细错误码信息</li> <li>requestUrl: 字符串型, 当前请求的 url 信息</li></ul> <p>但是比较麻烦的是怎么在全局异常处理中获得上述 4 种信息, 1 种是可以利用获得的 error 实例, 在 error 实例上挂载相应的信息, 修改后的 user.js 代码如下</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 实例化router</span>

router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/v1/:id/test'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> path <span class="token operator">=</span> ctx<span class="token punctuation">.</span>params
  <span class="token keyword">const</span> query <span class="token operator">=</span> ctx<span class="token punctuation">.</span>request<span class="token punctuation">.</span>query
  <span class="token keyword">const</span> headers <span class="token operator">=</span> ctx<span class="token punctuation">.</span>request<span class="token punctuation">.</span>header
  <span class="token keyword">const</span> body <span class="token operator">=</span> ctx<span class="token punctuation">.</span>request<span class="token punctuation">.</span>body

  <span class="token comment">// 如果query为空对象, 则抛出相应错误</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token string">'{}'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 创建error对象之后, 在上面挂载相应的状态</span>
    <span class="token keyword">const</span> error <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'错误信息'</span><span class="token punctuation">)</span>
    error<span class="token punctuation">.</span>errorCode <span class="token operator">=</span> <span class="token number">10001</span>
    error<span class="token punctuation">.</span>status <span class="token operator">=</span> <span class="token number">400</span>
    error<span class="token punctuation">.</span>requestUrl <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>ctx<span class="token punctuation">.</span>method<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>ctx<span class="token punctuation">.</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span>
    <span class="token keyword">throw</span> error
  <span class="token punctuation">}</span>
  <span class="token comment">// 如果没有错误发生, 则显示&quot;获取参数成功&quot;</span>
  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> key<span class="token operator">:</span> <span class="token string">'获取参数成功'</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><div class="custom-block tip"><p class="custom-block-title">小技巧</p> <p>判断一个对象<code>obj</code>是不是空对象<code>{}</code>, 没法用<code>if(!obj){}</code>进行判断, 可以采用<code>JSON.stringify(obj) === '{}'</code>进行判断</p></div> <p>此时, 全局异常处理<code>exception.js</code>代码可以修改为:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 自己编写全局异常处理, 有点面向切面编程的感觉</span>
<span class="token keyword">const</span> <span class="token function-variable function">catchError</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 如果含有errorCode, 表示是一类已知错误</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">.</span>errorCode<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// 构造返回值</span>
      ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span>
        msg<span class="token operator">:</span> error<span class="token punctuation">.</span>message<span class="token punctuation">,</span>
        errorCode<span class="token operator">:</span> error<span class="token punctuation">.</span>errorCode<span class="token punctuation">,</span>
        requestUrl<span class="token operator">:</span> error<span class="token punctuation">.</span>requestUrl<span class="token punctuation">,</span>
      <span class="token punctuation">}</span>
      <span class="token comment">// http状态码直接写到ctx上</span>
      ctx<span class="token punctuation">.</span>status <span class="token operator">=</span> error<span class="token punctuation">.</span>status
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> catchError
</code></pre></div><p>但是, 采用这种方式, 每次构造异常比较麻烦, 可以采用类的方式简化这种构建.</p> <h3 id="使用类的方式改进异常生成"><a href="#使用类的方式改进异常生成" class="header-anchor">#</a> 使用类的方式改进异常生成</h3> <p>在<code>core</code>文件夹下面新增<code>http-exception.js</code>, 写法如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 必须要继承内置类Error, 否则无法抛出自定义的这个类</span>
<span class="token keyword">class</span> <span class="token class-name">HttpException</span> <span class="token keyword">extends</span> <span class="token class-name">Error</span> <span class="token punctuation">{</span>
  <span class="token comment">// 设置默认值</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span>msg <span class="token operator">=</span> <span class="token string">'服务器错误'</span><span class="token punctuation">,</span> errorCode <span class="token operator">=</span> <span class="token number">10000</span><span class="token punctuation">,</span> code <span class="token operator">=</span> <span class="token number">400</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 调用父类的构造方法</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>errorCode <span class="token operator">=</span> errorCode
    <span class="token keyword">this</span><span class="token punctuation">.</span>code <span class="token operator">=</span> code
    <span class="token keyword">this</span><span class="token punctuation">.</span>msg <span class="token operator">=</span> msg
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// 使用对象的方式导出, 这样可以导出多个异常</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  HttpException<span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre></div><p>然后在<code>user.js</code>中, 就可以使用这个异常类构建 <code>httpException</code>.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>
<span class="token comment">// 导入自定义Http异常类</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> HttpException <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../../core/http-exception'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 实例化router</span>

router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/v1/:id/test'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> path <span class="token operator">=</span> ctx<span class="token punctuation">.</span>params
  <span class="token keyword">const</span> query <span class="token operator">=</span> ctx<span class="token punctuation">.</span>request<span class="token punctuation">.</span>query
  <span class="token keyword">const</span> headers <span class="token operator">=</span> ctx<span class="token punctuation">.</span>request<span class="token punctuation">.</span>header
  <span class="token keyword">const</span> body <span class="token operator">=</span> ctx<span class="token punctuation">.</span>request<span class="token punctuation">.</span>body

  <span class="token comment">// 如果query为空对象, 则抛出相应错误</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token string">'{}'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 创建error对象之后, 在上面挂载相应的状态</span>
    <span class="token keyword">const</span> error <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HttpException</span><span class="token punctuation">(</span><span class="token string">'参数错误'</span><span class="token punctuation">,</span> <span class="token number">10001</span><span class="token punctuation">,</span> <span class="token number">400</span><span class="token punctuation">)</span>
    <span class="token keyword">throw</span> error
  <span class="token punctuation">}</span>
  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> key<span class="token operator">:</span> <span class="token string">'获取参数成功'</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><p>在全局异常处理<code>exception.js</code>里面, 对判断和 requestUrl 也进行了一定的改善, 代码如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 导入HttpException类, 以便进行判定</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> HttpException <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../core/http-exception'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> <span class="token function-variable function">catchError</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 如果是httpException, 则属于已知错误</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>error <span class="token keyword">instanceof</span> <span class="token class-name">HttpException</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// 构造返回值</span>
      ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span>
        msg<span class="token operator">:</span> error<span class="token punctuation">.</span>msg<span class="token punctuation">,</span>
        errorCode<span class="token operator">:</span> error<span class="token punctuation">.</span>errorCode<span class="token punctuation">,</span>
        requestUrl<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>ctx<span class="token punctuation">.</span>method<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>ctx<span class="token punctuation">.</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span>
      <span class="token comment">// http状态码直接写到ctx上</span>
      ctx<span class="token punctuation">.</span>status <span class="token operator">=</span> error<span class="token punctuation">.</span>code
    <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
      <span class="token comment">// 处理未知异常</span>
      ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span>
        msg<span class="token operator">:</span> <span class="token string">'服务器错误'</span><span class="token punctuation">,</span>
        errorCode<span class="token operator">:</span> <span class="token number">999</span><span class="token punctuation">,</span>
        requestUrl<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>ctx<span class="token punctuation">.</span>method<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>ctx<span class="token punctuation">.</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
      <span class="token punctuation">}</span>
      ctx<span class="token punctuation">.</span>status <span class="token operator">=</span> <span class="token number">500</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> catchError
</code></pre></div><h3 id="定义多种异常类方便使用"><a href="#定义多种异常类方便使用" class="header-anchor">#</a> 定义多种异常类方便使用</h3> <p>采用类的方式定义了<code>HttpException</code>类之后, 可以再次基础上进行继承和派生类, 方便使用, 这也是使用类的方式进行代码编写的好处, 例如我们可以针对参数错误类, 定义一个<code>ParameterException</code>, 这时候<code>http-exception.js</code>改写为:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">class</span> <span class="token class-name">HttpException</span> <span class="token keyword">extends</span> <span class="token class-name">Error</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span>msg <span class="token operator">=</span> <span class="token string">'服务器错误'</span><span class="token punctuation">,</span> errorCode <span class="token operator">=</span> <span class="token number">10000</span><span class="token punctuation">,</span> code <span class="token operator">=</span> <span class="token number">400</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>errorCode <span class="token operator">=</span> errorCode
    <span class="token keyword">this</span><span class="token punctuation">.</span>code <span class="token operator">=</span> code
    <span class="token keyword">this</span><span class="token punctuation">.</span>msg <span class="token operator">=</span> msg
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// 新定义参数错误类, 继承自HttpException</span>
<span class="token keyword">class</span> <span class="token class-name">ParameterException</span> <span class="token keyword">extends</span> <span class="token class-name">HttpException</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span>msg <span class="token operator">=</span> <span class="token string">'参数错误'</span><span class="token punctuation">,</span> errorCode <span class="token operator">=</span> <span class="token number">10000</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>code <span class="token operator">=</span> <span class="token number">400</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>msg <span class="token operator">=</span> msg
    <span class="token keyword">this</span><span class="token punctuation">.</span>errorCode <span class="token operator">=</span> errorCode
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">// 使用对象的方式导出多个异常</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> HttpException<span class="token punctuation">,</span> ParameterException <span class="token punctuation">}</span>
</code></pre></div><p>在<code>user.js</code>代码中使用的时候只需要创建该异常即可:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 如果query为空对象, 则抛出参数错误类</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token string">'{}'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// 新建参数错误异常类并抛出</span>
  <span class="token keyword">const</span> error <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ParameterException</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token keyword">throw</span> error
<span class="token punctuation">}</span>
</code></pre></div><h3 id="区分生产环境和开发环境"><a href="#区分生产环境和开发环境" class="header-anchor">#</a> 区分生产环境和开发环境</h3> <p>在开发环境中, 对于未知异常, 我们期望程序抛出它并可以给我们查看, 但是在生产环境中则不需要这样, 因此需要新建配置文件来区分这两种开发环境.<br>
在根目录中新建<code>config/config.js</code>, 写入配置:</p> <div class="language-js extra-class"><pre class="language-js"><code>module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token comment">// 如果prod则是生产环境, 如果是dev则是开发环境</span>
  enviroment<span class="token operator">:</span> <span class="token string">'dev'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre></div><p>在项目初始化时将配置加载到全局变量<code>global</code>中, 需要改写<code>core/init.js</code>代码</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> requireDirectory <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'require-directory'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>

<span class="token keyword">class</span> <span class="token class-name">InitManager</span> <span class="token punctuation">{</span>
  <span class="token comment">// 初始化类</span>
  <span class="token keyword">static</span> <span class="token function">initCore</span><span class="token punctuation">(</span><span class="token parameter">app</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    InitManager<span class="token punctuation">.</span>app <span class="token operator">=</span> app
    InitManager<span class="token punctuation">.</span><span class="token function">initLoadRouters</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token comment">// 初始化时加载全局配置</span>
    InitManager<span class="token punctuation">.</span><span class="token function">loadConfig</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 加载路由</span>
  <span class="token keyword">static</span> <span class="token function">initLoadRouters</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span>

  <span class="token comment">// 在全局变量中加载config</span>
  <span class="token keyword">static</span> <span class="token function">loadConfig</span><span class="token punctuation">(</span>path <span class="token operator">=</span> <span class="token string">''</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> configPath <span class="token operator">=</span> path <span class="token operator">||</span> process<span class="token punctuation">.</span><span class="token function">cwd</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">'/config/config.js'</span>
    <span class="token keyword">const</span> config <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span>configPath<span class="token punctuation">)</span>
    global<span class="token punctuation">.</span>config <span class="token operator">=</span> config
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> InitManager

</code></pre></div><p>然后在<code>middlewares/exception.js</code>中, 加入当前代码环境的判断</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token punctuation">{</span> HttpException <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../core/http-exception'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> <span class="token function-variable function">catchError</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">try</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 区分开发环境和生产环境, 如果是开发环境, 而且不属于HttpException, 则提示错误信息</span>
    <span class="token keyword">const</span> isHttpException <span class="token operator">=</span> error <span class="token keyword">instanceof</span> <span class="token class-name">HttpException</span>
    <span class="token keyword">const</span> isDev <span class="token operator">=</span> global<span class="token punctuation">.</span>config<span class="token punctuation">.</span>enviroment <span class="token operator">===</span> <span class="token string">'dev'</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isHttpException <span class="token operator">&amp;&amp;</span> isDev<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> error
    <span class="token punctuation">}</span>
    <span class="token comment">// 如果是httpException, 则属于已知错误</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>isHttpException<span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> catchError

</code></pre></div><h2 id="参数校验-validator"><a href="#参数校验-validator" class="header-anchor">#</a> 参数校验(Validator)</h2> <h3 id="使用校验器"><a href="#使用校验器" class="header-anchor">#</a> 使用校验器</h3> <p>koa2 没有特别好的校验器, 目前使用的是<a href="https://github.com/hpmax00/lin-mizar" target="_blank" rel="noopener noreferrer">lin-mizar<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>提供的 validator 类, lin-mizar 是<a href="https://github.com/TaleLin/lin-cms-koa" target="_blank" rel="noopener noreferrer">LinCms<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>的核心库, 首先下载<a href="https://wws.lanzous.com/inTEFgja8cf" target="_blank" rel="noopener noreferrer">lin-validator.zip<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>, 解压之后放到<code>core</code>文件夹下, 然后在<code>app</code>目录新建<code>validators</code>目录及<code>validator.js</code>文件, 目录结构如下:</p> <div class="language-bash extra-class"><pre class="language-bash"><code>my_koa2
 ├── app
 │   ├── api
 │   │   └── v1
 │   │       ├── deploy.js
 │   │       └── user.js
 │   ├── lib
 │   └── validators
 │       └── validator.js
 ├── app.js
 ├── config
 │   └── config.js
 ├── core
 │   ├── http-exception.js
 │   ├── init.js
 │   ├── lin-validator.js
 │   └── util.js
 └── middlewares
     └── exception.js
</code></pre></div><p><code>validator.js</code>中写校验器代码:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token punctuation">{</span> LinValidator<span class="token punctuation">,</span> Rule <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../core/lin-validator'</span><span class="token punctuation">)</span>

<span class="token comment">// 正整数校验器</span>
<span class="token keyword">class</span> <span class="token class-name">PositiveIntegerValidator</span> <span class="token keyword">extends</span> <span class="token class-name">LinValidator</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token comment">// this.id值表示校验的是id参数,</span>
    <span class="token comment">// 由于是数组, 所以可以定义多个校验规则, 它们是&quot;且&quot;关系</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>id <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token keyword">new</span> <span class="token class-name">Rule</span><span class="token punctuation">(</span><span class="token string">'isInt'</span><span class="token punctuation">,</span> <span class="token string">'需要正整数'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> min<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  PositiveIntegerValidator<span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre></div><p>在<code>user.js</code>中使用也比较简单</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>
<span class="token comment">// 导入正整数校验器</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> PositiveIntegerValidator <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../validators/validator'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 实例化router</span>

router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/v1/:id/test'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// 实例化校验器后, 校验时需传入ctx参数</span>
  <span class="token comment">// 因为所有的参数都保存在ctx中,所以必须要传入ctx</span>
  <span class="token keyword">const</span> v <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PositiveIntegerValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>

  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> key<span class="token operator">:</span> <span class="token string">'获取参数成功'</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><p>这时, 使用 postman 发送请求<code>localhost:3000/v1/-1/test?param=weiwei</code>, 由于 id 给了-1, 则自动返回错误提示信息</p> <div class="language-json extra-class"><pre class="language-json"><code><span class="token punctuation">{</span>
  <span class="token property">&quot;msg&quot;</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">&quot;id需要正整数&quot;</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token property">&quot;errorCode&quot;</span><span class="token operator">:</span> <span class="token number">10000</span><span class="token punctuation">,</span>
  <span class="token property">&quot;requestUrl&quot;</span><span class="token operator">:</span> <span class="token string">&quot;POST /v1/-1/test&quot;</span>
<span class="token punctuation">}</span>
</code></pre></div><div class="custom-block warning"><p class="custom-block-title">注意</p> <p>由于我们要校验的是 id 参数, 所以在创建<code>PositiveIntegerValidator</code>时对<code>this.id</code>赋予了校验规则.<br>
校验规则, 例如&quot;isInt&quot;, 来自于<a href="https://github.com/validatorjs/validator.js" target="_blank" rel="noopener noreferrer">validator.js<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>开源库.</p></div> <h3 id="使用校验器获取参数"><a href="#使用校验器获取参数" class="header-anchor">#</a> 使用校验器获取参数</h3> <p>在<code>user.js</code>中使用校验器, 然后在校验器中获取定义的参数</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> PositiveIntegerValidator <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../validators/validator'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span>

router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/v1/:id/test'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> v <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">PositiveIntegerValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>
  <span class="token comment">// 如果校验器通过了, 可以利用校验器获取参数,</span>
  <span class="token comment">// 分别用path,query,head,body代表路径,查询,head和body中的参数</span>
  <span class="token comment">// 例如path.id代表获取路径中的id参数</span>
  <span class="token comment">// validator会自动进行转型, 如果不需要转型则第2个参数传false</span>
  <span class="token comment">// 也能获取嵌套层级, 例如'body.a.b'</span>
  <span class="token keyword">const</span> id <span class="token operator">=</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'path.id'</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span>

  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> key<span class="token operator">:</span> <span class="token string">'获取参数成功'</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><h2 id="数据持久化"><a href="#数据持久化" class="header-anchor">#</a> 数据持久化</h2> <h3 id="安装-mysql-与-navicat"><a href="#安装-mysql-与-navicat" class="header-anchor">#</a> 安装 mysql 与 navicat</h3> <p>通过访问<a href="https://www.apachefriends.org/zh_cn/index.html" target="_blank" rel="noopener noreferrer">XAMPP<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>, 下载安装 XAMPP, 即自带 MariaDB. <a href="https://mariadb.org/" target="_blank" rel="noopener noreferrer">MariaDB<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a> 数据库管理系统是 MySQL 的一个分支，主要由开源社区在维护，采用 GPL 授权许可 MariaDB 的目的是完全兼容 MySQL，包括 API 和命令行，使之能轻松成为 MySQL 的代替品。</p> <p>此外, 还需要安装<a href="https://navicat.com.cn/" target="_blank" rel="noopener noreferrer">navicat for mysql<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>, 他是一个数据库可视化管理工具.</p> <p>安装 XAMPP 完毕后, 登陆 XAMPP 管理控制台, 开启 mysql 数据库.<br> <img src="https://s1.ax1x.com/2020/04/26/JcdLvV.png" alt="XAMPP管理控制台"><br>
这时,我们需要使用 navicat 修改<code>root</code>用户的密码, 用 navicat 首次登陆不输入密码, 点击[用户]菜单, 修改所有 root 开头的用户名, 然后就可以按照新密码进行登陆了.<br> <img src="https://s1.ax1x.com/2020/04/26/Jcd4Hg.png" alt="navicat修改用户密码"></p> <h3 id="使用-sequelize-创建-user-表"><a href="#使用-sequelize-创建-user-表" class="header-anchor">#</a> 使用 Sequelize 创建 User 表</h3> <p>Sequlize 是 node.js 的一个 ORM 框架, 详细信息可以查阅<a href="https://github.com/demopark/sequelize-docs-Zh-CN/tree/v5" target="_blank" rel="noopener noreferrer">v5 版本中文 api 文档<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>,首先在配置文件中设置数据库用户名, 密码, 地址等参数:</p> <div class="language-js extra-class"><pre class="language-js"><code>module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token comment">// 如果prod则是生产环境, 如果是dev则是开发环境</span>
  enviroment<span class="token operator">:</span> <span class="token string">'dev'</span><span class="token punctuation">,</span>
  database<span class="token operator">:</span> <span class="token punctuation">{</span>
    dbName<span class="token operator">:</span> <span class="token string">'filemsg'</span><span class="token punctuation">,</span>
    host<span class="token operator">:</span> <span class="token string">'127.0.0.1'</span><span class="token punctuation">,</span>
    port<span class="token operator">:</span> <span class="token number">3306</span><span class="token punctuation">,</span>
    user<span class="token operator">:</span> <span class="token string">'root'</span><span class="token punctuation">,</span>
    password<span class="token operator">:</span> <span class="token string">'123456'</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre></div><p>在<code>core</code>文件夹下面新建<code>db.js</code>文件夹, 这里使用的 ORM 库是<a href="https://sequelize.org/v5/" target="_blank" rel="noopener noreferrer">sequelize<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>, 该文件主要完成配置数据库连接以及初始化等一系列操作:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 引入sequelize</span>
<span class="token keyword">const</span> Sequelize <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sequelize'</span><span class="token punctuation">)</span>

<span class="token comment">// 从配置文件中解构获取database参数</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span>
  dbName<span class="token punctuation">,</span>
  host<span class="token punctuation">,</span>
  port<span class="token punctuation">,</span>
  user<span class="token punctuation">,</span>
  password<span class="token punctuation">,</span>
<span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../config/config'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>database
<span class="token comment">// 创建Sequelize实例</span>
<span class="token keyword">const</span> sequelize <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Sequelize</span><span class="token punctuation">(</span>dbName<span class="token punctuation">,</span> user<span class="token punctuation">,</span> password<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  dialect<span class="token operator">:</span> <span class="token string">'mysql'</span><span class="token punctuation">,</span> <span class="token comment">// 设置数据库语言别名为&quot;mysql&quot;</span>
  host<span class="token punctuation">,</span>
  port<span class="token punctuation">,</span>
  logging<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 显示日志, 包括操作的SQL语句</span>
  timezone<span class="token operator">:</span> <span class="token string">'+8:00'</span><span class="token punctuation">,</span>
  define<span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token comment">// 是否显示createdAt和updateAt时间戳字段</span>
    timestamps<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    paranoid<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 会增加deleteAt字段, 实现假删除</span>
    <span class="token comment">// 下面3项是更改别名</span>
    createdAt<span class="token operator">:</span> <span class="token string">'created_at'</span><span class="token punctuation">,</span>
    updatedAt<span class="token operator">:</span> <span class="token string">'updated_at'</span><span class="token punctuation">,</span>
    deletedAt<span class="token operator">:</span> <span class="token string">'deleted_at'</span><span class="token punctuation">,</span>
    <span class="token comment">// 默认命名使用驼峰式命名，该配置则使用蛇型命名。</span>
    underscored<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 将定义的模型同步到数据库上, force表示强制更新, 会丢失数据</span>
sequelize<span class="token punctuation">.</span><span class="token function">sync</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  force<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 导出模型</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> sequelize <span class="token punctuation">}</span>
</code></pre></div><p>在配置好<code>core/db.js</code>之后, 可以在<code>app</code>下新建<code>models</code>文件夹, 用来存放所有的数据库模型类, 这里新建<code>models/user.js</code>:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token punctuation">{</span> Sequelize<span class="token punctuation">,</span> Model <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sequelize'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> sequelize <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../core/db'</span><span class="token punctuation">)</span>

<span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

User<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span>
    <span class="token comment">// 主键ID</span>
    id<span class="token operator">:</span> <span class="token punctuation">{</span>
      type<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">INTEGER</span><span class="token punctuation">,</span> <span class="token comment">// 整型</span>
      primaryKey<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 是否主键</span>
      autoIncrement<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// 自增</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    nickname<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">,</span> <span class="token comment">// 昵称</span>
    email<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">,</span> <span class="token comment">// 电子邮箱</span>
    password<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">,</span> <span class="token comment">// 密码</span>
    openid<span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token comment">// 更加详细的设置每个属性, 包括长度, 是否唯一等</span>
      type<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">(</span><span class="token number">64</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      unique<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token comment">// 第二个参数传递sequelize实例, 以及表名称</span>
  <span class="token punctuation">{</span> sequelize<span class="token punctuation">,</span> tableName<span class="token operator">:</span> <span class="token string">'user'</span> <span class="token punctuation">}</span>
<span class="token punctuation">)</span>
</code></pre></div><p>如果需要系统在运行之初执行数据代码的话, 还需要在<code>app.js</code>中引用<code>user.js</code>.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 在创建app之初, 先引用user.js</span>
<span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./app/models/user'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre></div><h3 id="user-表注册功能验证相关信息"><a href="#user-表注册功能验证相关信息" class="header-anchor">#</a> User 表注册功能验证相关信息</h3> <p>在<code>app/validators/validator.js</code>中增加用户注册的验证器:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token punctuation">{</span> LinValidator<span class="token punctuation">,</span> Rule <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../core/lin-validator'</span><span class="token punctuation">)</span>

<span class="token keyword">class</span> <span class="token class-name">RegisterValidator</span> <span class="token keyword">extends</span> <span class="token class-name">LinValidator</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>email <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token keyword">new</span> <span class="token class-name">Rule</span><span class="token punctuation">(</span><span class="token string">'isEmail'</span><span class="token punctuation">,</span> <span class="token string">'不符合email规范'</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token comment">// 密码1的校验规则</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>password1 <span class="token operator">=</span> <span class="token punctuation">[</span>
      <span class="token keyword">new</span> <span class="token class-name">Rule</span><span class="token punctuation">(</span><span class="token string">'isLength'</span><span class="token punctuation">,</span> <span class="token string">'密码至少6个字符, 最多32个'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> min<span class="token operator">:</span> <span class="token number">6</span><span class="token punctuation">,</span> max<span class="token operator">:</span> <span class="token number">32</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token keyword">new</span> <span class="token class-name">Rule</span><span class="token punctuation">(</span>
        <span class="token string">'matches'</span><span class="token punctuation">,</span>
        <span class="token string">'密码不符合规范'</span><span class="token punctuation">,</span>
        <span class="token string">'^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]'</span>
      <span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span>
    <span class="token comment">// 由于校验规则与pwd1相同,直接复制</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>password2 <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>password1
    <span class="token keyword">this</span><span class="token punctuation">.</span>nickname <span class="token operator">=</span> <span class="token punctuation">[</span>
      <span class="token keyword">new</span> <span class="token class-name">Rule</span><span class="token punctuation">(</span><span class="token string">'isLength'</span><span class="token punctuation">,</span> <span class="token string">'昵称至少4个字符, 最多32个'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> min<span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">,</span> max<span class="token operator">:</span> <span class="token number">32</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// 自定义校验规则, 函数必须以validate开头</span>
  <span class="token comment">// 判断两个密码是否相同</span>
  <span class="token function">validatePassword</span><span class="token punctuation">(</span><span class="token parameter">vals</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> pw1 <span class="token operator">=</span> vals<span class="token punctuation">.</span>body<span class="token punctuation">.</span>password1
    <span class="token keyword">const</span> pw2 <span class="token operator">=</span> vals<span class="token punctuation">.</span>body<span class="token punctuation">.</span>password2
    <span class="token keyword">if</span> <span class="token punctuation">(</span>pw1 <span class="token operator">!==</span> pw2<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment">// 抛出普通异常, 由LinValidator来进行处理</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'两个密码必须相同'</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  RegisterValidator<span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre></div><p>修改<code>user.js</code>, 增加注册的接口:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> RegisterValidator <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../validators/validator'</span><span class="token punctuation">)</span>

<span class="token comment">// 设定路由前缀, 这样写路由可以避免重复前面的地址</span>
<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  prefix<span class="token operator">:</span> <span class="token string">'/v1/user'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 注册, 不需要next, 所以没传</span>
<span class="token comment">// 编写一个接口需要利用validator接收参数</span>
router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/register'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> v <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">RegisterValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><h3 id="增加-email-规则校验"><a href="#增加-email-规则校验" class="header-anchor">#</a> 增加 email 规则校验</h3> <p>为了保证用户提交的 email 与其他用户不相同, 因此在验证时增加了自定义的 email 校验, 这里需要注意的是由于需要在数据库中进行查询, 所以使用了 async/await.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token punctuation">{</span> LinValidator<span class="token punctuation">,</span> Rule <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../core/lin-validator'</span><span class="token punctuation">)</span>
<span class="token comment">// 导入用户模块</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> User <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../models/user'</span><span class="token punctuation">)</span>

<span class="token keyword">class</span> <span class="token class-name">PositiveIntegerValidator</span> <span class="token keyword">extends</span> <span class="token class-name">LinValidator</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span>

<span class="token keyword">class</span> <span class="token class-name">RegisterValidator</span> <span class="token keyword">extends</span> <span class="token class-name">LinValidator</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span>

  <span class="token function">validatePassword</span><span class="token punctuation">(</span><span class="token parameter">vals</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span>

  <span class="token comment">// 自定义email校验, 需要用validate开头, 保证email不与数据库中的值重复</span>
  <span class="token comment">// 数据库操作都是异步, 所以都需要加async/await</span>
  <span class="token keyword">async</span> <span class="token function">validateEmail</span><span class="token punctuation">(</span><span class="token parameter">vals</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> email <span class="token operator">=</span> vals<span class="token punctuation">.</span>body<span class="token punctuation">.</span>email
    <span class="token comment">// 找到email相同的用户</span>
    <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> User<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      where<span class="token operator">:</span> <span class="token punctuation">{</span>
        email<span class="token operator">:</span> email<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'email已经存在'</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span>

</code></pre></div><h3 id="user-表保存信息到数据库"><a href="#user-表保存信息到数据库" class="header-anchor">#</a> User 表保存信息到数据库</h3> <p>拿到获取的到用户信息, 使用<code>User.create(user)</code>进行保存操作:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> RegisterValidator <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../validators/validator'</span><span class="token punctuation">)</span>
<span class="token comment">// 引入User模块</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> User <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../models/user'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  prefix<span class="token operator">:</span> <span class="token string">'/v1/user'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/register'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// 由于email操作是异步的, 所以需要加上await</span>
  <span class="token comment">// 一般来说, 所有的validator都需要加上await</span>
  <span class="token comment">// validate需要放到代码的第一行, 否则起不到守门的作用</span>
  <span class="token keyword">const</span> v <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">RegisterValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>
  <span class="token comment">// 获取用户参数信息, 由于已经验证过了, 所以直接可以用</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token punctuation">{</span>
    email<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.email'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    password<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.password1'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    nickname<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.email'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 把用户保存至User表中, create是异步调用, 返回一个promise对象, 需要用await接收</span>
  <span class="token keyword">const</span> newUser <span class="token operator">=</span> <span class="token keyword">await</span> User<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><h3 id="在模型中对-password-密码加密"><a href="#在模型中对-password-密码加密" class="header-anchor">#</a> 在模型中对 password 密码加密</h3> <p>在<code>models/user.js</code>的用户模型类中,利用 <code>password</code>中的<code>set()</code> 方法, 始终观察 password 情况,进行加密. 加密采用的是<code>bcryptjs</code>模块, 因为采用了加盐处理, 所以即使相同的密码, 保存到数据库中都不一样, 其中<em>How bcryptjs works</em>这篇文章详细解释了它的工作原理</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> bcrypt <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'bcryptjs'</span><span class="token punctuation">)</span> <span class="token comment">// 导入加密模块</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> Sequelize<span class="token punctuation">,</span> Model <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sequelize'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> sequelize <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../core/db'</span><span class="token punctuation">)</span>

<span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

User<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span>
    id<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
    nickname<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">,</span>
    email<span class="token operator">:</span> <span class="token punctuation">{</span>
      type<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">(</span><span class="token number">128</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      unique<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    password<span class="token operator">:</span> <span class="token punctuation">{</span>
      type<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">,</span>
      <span class="token comment">// set函数会在给password赋值的时候调用</span>
      <span class="token comment">// 这里相当于实现了一个观察者模式</span>
      <span class="token function">set</span><span class="token punctuation">(</span>val<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 传的参数表示生成盐的成本, 通常用10, 使用同步版本</span>
        <span class="token keyword">const</span> salt <span class="token operator">=</span> bcrypt<span class="token punctuation">.</span><span class="token function">genSaltSync</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span>
        <span class="token comment">// 两个用户密码即使一样, 加密后的密码也应该不一样, 以防止彩虹攻击</span>
        <span class="token keyword">const</span> psw <span class="token operator">=</span> bcrypt<span class="token punctuation">.</span><span class="token function">hashSync</span><span class="token punctuation">(</span>val<span class="token punctuation">,</span> salt<span class="token punctuation">)</span>
        <span class="token comment">// 将加密过后的密码存入数据库</span>
        <span class="token comment">// setDataValue是Model模型中的方法, 第一个参数表示给哪个参数赋值</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setDataValue</span><span class="token punctuation">(</span><span class="token string">'password'</span><span class="token punctuation">,</span> psw<span class="token punctuation">)</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    openid<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span> sequelize<span class="token punctuation">,</span> tableName<span class="token operator">:</span> <span class="token string">'user'</span> <span class="token punctuation">}</span>
<span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> User <span class="token punctuation">}</span>
</code></pre></div><h3 id="处理操作成功-success-的返回情况"><a href="#处理操作成功-success-的返回情况" class="header-anchor">#</a> 处理操作成功(Success)的返回情况</h3> <p>在<code>http-exception.js</code>中, 增加<code>Success</code>的异常类, 如果成功则直接由它返回.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 处理成功的信息</span>
<span class="token keyword">class</span> <span class="token class-name">Success</span> <span class="token keyword">extends</span> <span class="token class-name">HttpException</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">msg<span class="token punctuation">,</span> errorCode</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>code <span class="token operator">=</span> <span class="token number">200</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>msg <span class="token operator">=</span> msg <span class="token operator">||</span> <span class="token string">'操作成功'</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>errorCode <span class="token operator">=</span> errorCode <span class="token operator">||</span> <span class="token number">0</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>在 api 的<code>user.js</code>中, 处理成功后直接抛出成功的类</p> <div class="language-js extra-class"><pre class="language-js"><code>router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/register'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> v <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">RegisterValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token punctuation">{</span>
    email<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.email'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    password<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.password1'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    nickname<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.email'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// create是异步调用, 返回一个promise对象, 需要用await接收</span>
  <span class="token keyword">await</span> User<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span>
  <span class="token comment">// 抛出成功的情况</span>
  <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Success</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre></div><h2 id="令牌发放"><a href="#令牌发放" class="header-anchor">#</a> 令牌发放</h2> <h3 id="定义-type-相关枚举类型-enum"><a href="#定义-type-相关枚举类型-enum" class="header-anchor">#</a> 定义 Type 相关枚举类型(Enum)</h3> <p>在<code>app/lib</code>文件夹下新建一个枚举功能类<code>enum.js</code>, 内容如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 判断val是否存在各类Type中</span>
<span class="token keyword">function</span> <span class="token function">isThisType</span><span class="token punctuation">(</span><span class="token parameter">val</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> key <span class="token keyword">in</span> <span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">===</span> val<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token boolean">true</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">return</span> <span class="token boolean">false</span>
<span class="token punctuation">}</span>
<span class="token comment">// 定义登录类型</span>
<span class="token comment">// 其中增加isThisType的判定,</span>
<span class="token comment">// 例如用户传100, 通过这个方法即可判断是否为可用值</span>
<span class="token keyword">const</span> LoginType <span class="token operator">=</span> <span class="token punctuation">{</span>
  <span class="token constant">USER_MINI_PROGRAM</span><span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span>
  <span class="token constant">USER_EMAIL</span><span class="token operator">:</span> <span class="token number">101</span><span class="token punctuation">,</span>
  <span class="token constant">USER_MOBILE</span><span class="token operator">:</span> <span class="token number">102</span><span class="token punctuation">,</span>
  <span class="token constant">ADMIN_EMAIL</span><span class="token operator">:</span> <span class="token number">200</span><span class="token punctuation">,</span>
  isThisType<span class="token punctuation">,</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  LoginType<span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre></div><h3 id="验证用户邮箱和密码是否正确"><a href="#验证用户邮箱和密码是否正确" class="header-anchor">#</a> 验证用户邮箱和密码是否正确</h3> <p>在<code>app/api/v1</code>下新建<code>token.js</code>的令牌相关 api 类, 里面主要写颁布令牌的功能, 首先需要在<code>validator.js</code>中增加<code>TokenValidator.js</code>的验证类.这里的关键在于<strong>可选值</strong>的验证, 以及必须在枚举类型中的验证方法.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">class</span> <span class="token class-name">TokenValidator</span> <span class="token keyword">extends</span> <span class="token class-name">LinValidator</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token comment">// 账号传入校验</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>account <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token keyword">new</span> <span class="token class-name">Rule</span><span class="token punctuation">(</span><span class="token string">'isLength'</span><span class="token punctuation">,</span> <span class="token string">'不符合账号规则'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> min<span class="token operator">:</span> <span class="token number">4</span><span class="token punctuation">,</span> max<span class="token operator">:</span> <span class="token number">32</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">]</span>
    <span class="token comment">// 密码是可选校验</span>
    <span class="token comment">// isOptional表示可选参数, 这是lin-validator自带的</span>
    <span class="token comment">// 如果传入了, 则需要保证`isLength`规定的校验规则</span>
    <span class="token comment">// 如果不传 则默认值就是123456</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>secret <span class="token operator">=</span> <span class="token punctuation">[</span>
      <span class="token keyword">new</span> <span class="token class-name">Rule</span><span class="token punctuation">(</span><span class="token string">'isOptional'</span><span class="token punctuation">,</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token string">'123456'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      <span class="token keyword">new</span> <span class="token class-name">Rule</span><span class="token punctuation">(</span><span class="token string">'isLength'</span><span class="token punctuation">,</span> <span class="token string">'至少6个字符'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> min<span class="token operator">:</span> <span class="token number">6</span><span class="token punctuation">,</span> max<span class="token operator">:</span> <span class="token number">128</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">]</span>
  <span class="token punctuation">}</span>

  <span class="token comment">// 自定义验证器验证type情况, 需要保证在LoginType枚举中</span>
  <span class="token function">validateLoginType</span><span class="token punctuation">(</span><span class="token parameter">vals</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>vals<span class="token punctuation">.</span>body<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'type是必填项'</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>LoginType<span class="token punctuation">.</span><span class="token function">isThisType</span><span class="token punctuation">(</span>vals<span class="token punctuation">.</span>body<span class="token punctuation">.</span>type<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'type参数不合法'</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>使用<code>TokenValidator</code>验证之后, 根据<code>type</code>不同, 需要调用不同的处理函数, 如果都没有,则需要抛出异常.<br>
以邮箱和密码验证为例, 这里需要验证邮箱对应的用户是否存在, 以及用户传入的密码是否正确, 这里由于跟数据库操作相关, 因此放到模型<code>User</code>中进行.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> TokenValidator <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../validators/validator'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> LoginType <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../lib/enum'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> User <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../models/user'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> ParameterException <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../../core/http-exception'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  prefix<span class="token operator">:</span> <span class="token string">'/v1/token'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 颁布令牌</span>
router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> v <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">TokenValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>
  <span class="token keyword">const</span> account <span class="token operator">=</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.account'</span><span class="token punctuation">)</span> <span class="token comment">// 用户账户名</span>
  <span class="token keyword">const</span> secret <span class="token operator">=</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.secret'</span><span class="token punctuation">)</span> <span class="token comment">// 用户密码</span>
  <span class="token keyword">const</span> type <span class="token operator">=</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.type'</span><span class="token punctuation">)</span> <span class="token comment">// 用户登录方式</span>
  <span class="token comment">// 使用jwt令牌, 是随机字符串并可携带数据</span>
  <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">case</span> LoginType<span class="token punctuation">.</span><span class="token constant">USER_EMAIL</span><span class="token operator">:</span>
      <span class="token keyword">await</span> <span class="token function">emailLogin</span><span class="token punctuation">(</span>account<span class="token punctuation">,</span> secret<span class="token punctuation">)</span>
      <span class="token keyword">break</span>
    <span class="token keyword">case</span> LoginType<span class="token punctuation">.</span><span class="token constant">USER_MINI_PROGRAM</span><span class="token operator">:</span>
      <span class="token keyword">break</span>
    <span class="token keyword">default</span><span class="token operator">:</span>
      <span class="token comment">// 如果都没有处理, 则抛出异常</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ParameterException</span><span class="token punctuation">(</span><span class="token string">'没有相应的处理函数'</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">emailLogin</span><span class="token punctuation">(</span><span class="token parameter">account<span class="token punctuation">,</span> secret</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// 校验用户email和密码的信息, 放到模型User类中</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> User<span class="token punctuation">.</span><span class="token function">verifyEmailPassword</span><span class="token punctuation">(</span>account<span class="token punctuation">,</span> secret<span class="token punctuation">)</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><p>模型<code>User</code>中关于校验用户 email 和密码的信息, 之前<code>User</code>类只是继承<code>Model</code>类, 里面一直没有写方法, 这里加入邮箱密码是否正确的方法, 该方法为静态方法, 不需要实例化对象, 另外异常类由于内容都差不多, 这里就不展开了, <code>User</code>类中的写法如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span>
  <span class="token comment">// 验证用户密码是否正确, 静态方法, 方便调用</span>
  <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token function">verifyEmailPassword</span><span class="token punctuation">(</span><span class="token parameter">email<span class="token punctuation">,</span> plainPassword</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// 判断用户名是这个邮箱的用户存在否</span>
    <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      where<span class="token operator">:</span> <span class="token punctuation">{</span>
        email<span class="token operator">:</span> email<span class="token punctuation">,</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">AuthFailed</span><span class="token punctuation">(</span><span class="token string">'账号不存在'</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 使用bcrypt进行密码比对</span>
    <span class="token keyword">const</span> correct <span class="token operator">=</span> bcrypt<span class="token punctuation">.</span><span class="token function">compareSync</span><span class="token punctuation">(</span>plainPassword<span class="token punctuation">,</span> user<span class="token punctuation">.</span>password<span class="token punctuation">)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>correct<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">AuthFailed</span><span class="token punctuation">(</span><span class="token string">'密码错误'</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 如果不出问题, 则返回该用户信息</span>
    <span class="token keyword">return</span> user
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><div class="custom-block warning"><p class="custom-block-title">注意</p> <p>由于很多函数都需要异步进行数据库操作, 因此大量用到了<code>async/await</code>, 这块千万要注意!</p></div> <h3 id="生成-jwt-令牌-token"><a href="#生成-jwt-令牌-token" class="header-anchor">#</a> 生成 jwt 令牌(token)</h3> <p>生成令牌需要用到私有 key 和过期时间, 这里我们配置到<code>config.js</code>中:</p> <div class="language-js extra-class"><pre class="language-js"><code>module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  enviroment<span class="token operator">:</span> <span class="token string">'dev'</span><span class="token punctuation">,</span>
  database<span class="token operator">:</span> <span class="token punctuation">{</span><span class="token operator">...</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
  security<span class="token operator">:</span> <span class="token punctuation">{</span>
    <span class="token comment">// 生成令牌所需的私有key</span>
    secretKey<span class="token operator">:</span> <span class="token string">'asdc123dad'</span><span class="token punctuation">,</span>
    <span class="token comment">// 令牌的过期时间, 以秒为单位, 这里用1个小时</span>
    expiresIn<span class="token operator">:</span> <span class="token number">60</span> <span class="token operator">*</span> <span class="token number">60</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre></div><p>然后在<code>core/util.js</code>中增加生成令牌的<code>generateToken</code>方法, 在令牌中写入用户 id 和用户权限范围(scope)</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 导入jsonwebtoken模块, 用以生成令牌</span>
<span class="token keyword">const</span> jwt <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'jsonwebtoken'</span><span class="token punctuation">)</span>

<span class="token comment">// 颁发令牌</span>
<span class="token keyword">const</span> <span class="token function-variable function">generateToken</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">uid<span class="token punctuation">,</span> scope</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment">// 从配置项中读取私有key和过期时间</span>
  <span class="token keyword">const</span> secretKey <span class="token operator">=</span> global<span class="token punctuation">.</span>config<span class="token punctuation">.</span>security<span class="token punctuation">.</span>secretKey
  <span class="token keyword">const</span> expiresIn <span class="token operator">=</span> global<span class="token punctuation">.</span>config<span class="token punctuation">.</span>security<span class="token punctuation">.</span>expiresIn
  <span class="token comment">// 第1个参数放置我们需要令牌携带的内容,包括用户id和权限范围</span>
  <span class="token comment">// 第2个参数传入私有key,</span>
  <span class="token comment">// 第3个参数放置令牌的配置项, 这里传入了过期时间</span>
  <span class="token comment">// 然后利用jwt进行签名生成令牌</span>
  <span class="token keyword">const</span> token <span class="token operator">=</span> jwt<span class="token punctuation">.</span><span class="token function">sign</span><span class="token punctuation">(</span><span class="token punctuation">{</span> uid<span class="token punctuation">,</span> scope <span class="token punctuation">}</span><span class="token punctuation">,</span> secretKey<span class="token punctuation">,</span> <span class="token punctuation">{</span> expiresIn <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token keyword">return</span> token
<span class="token punctuation">}</span>
</code></pre></div><p>最后, 在<code>api</code>的<code>token.js</code>中使用该方法生成令牌</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 导入生成令牌的方法</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> generateToken <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../../core/util'</span><span class="token punctuation">)</span>

<span class="token comment">// 颁布令牌</span>
router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> v <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">TokenValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>
  <span class="token keyword">const</span> account <span class="token operator">=</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.account'</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> secret <span class="token operator">=</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.secret'</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> type <span class="token operator">=</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.type'</span><span class="token punctuation">)</span>
  <span class="token keyword">let</span> token <span class="token comment">// token默认为空</span>

  <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">case</span> LoginType<span class="token punctuation">.</span><span class="token constant">USER_EMAIL</span><span class="token operator">:</span>
      <span class="token comment">// 返回jwt令牌</span>
      token <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">emailLogin</span><span class="token punctuation">(</span>account<span class="token punctuation">,</span> secret<span class="token punctuation">)</span>
      <span class="token keyword">break</span>
    <span class="token keyword">default</span><span class="token operator">:</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ParameterException</span><span class="token punctuation">(</span><span class="token string">'没有相应的处理函数'</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 将token传递给前端</span>
  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token punctuation">{</span> token <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">emailLogin</span><span class="token punctuation">(</span><span class="token parameter">account<span class="token punctuation">,</span> secret</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> User<span class="token punctuation">.</span><span class="token function">verifyEmailPassword</span><span class="token punctuation">(</span>account<span class="token punctuation">,</span> secret<span class="token punctuation">)</span>
  <span class="token comment">// 获得用户后生成令牌</span>
  <span class="token keyword">return</span> <span class="token function">generateToken</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span>id<span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><h3 id="jwt-令牌校验与取值"><a href="#jwt-令牌校验与取值" class="header-anchor">#</a> jwt 令牌校验与取值</h3> <p>因为令牌用的比较多, 所以可以采用中间件的形式进行编写, 在<code>middlewares</code>文件夹下新建<code>auth.js</code>文件, 这里采用 basicAuth 的授权方式, 用类的方式返回一个中间件函数.<br>
返回的中间件函数使用 jwt 的<code>verify()</code>方法对令牌值进行验证, 验证通过则把解码后的值保存在<code>ctx.auth</code>属性上</p> <div class="custom-block warning"><p class="custom-block-title">注意</p> <p>类中的 m 是用<code>get</code>修饰, 因此它是一个<strong>属性</strong>, 只是在取<code>m</code>属性的时候会调用定义的 m()方法, 获取返回值. 返回值是一个中间件函数. 因此在用的时候, 应该是<code>new Auth().m</code>即可获取中间件函数</p></div> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 导入basicAuth相关的包</span>
<span class="token keyword">const</span> basicAuth <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'basic-auth'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> Forbbiden <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../core/http-exception'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> jwt <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'jsonwebtoken'</span><span class="token punctuation">)</span>

<span class="token keyword">class</span> <span class="token class-name">Auth</span> <span class="token punctuation">{</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

  <span class="token comment">// 注意,m是一个属性, 这里用get修饰符</span>
  <span class="token keyword">get</span> <span class="token function">m</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// token进行检查, 使用httpBasicAuth进行身份验证, 在username中传递令牌</span>
    <span class="token keyword">return</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token comment">// ctx.req获得的是node.js原生的request对象,</span>
      <span class="token comment">// ctx.request获取的则是koa2中封装的request对象</span>
      <span class="token comment">// 将request对象传入, 即可得到basicAuth的令牌</span>
      <span class="token comment">// basicAuth的令牌包括2个部分,一个是name, 一个是pass</span>
      <span class="token keyword">const</span> userToken <span class="token operator">=</span> <span class="token function">basicAuth</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>req<span class="token punctuation">)</span>
      <span class="token keyword">let</span> errMsg <span class="token operator">=</span> <span class="token string">'token不合法'</span>
      <span class="token comment">// 如果令牌不存在, 或者没有那么属性, 则抛出异常</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>userToken <span class="token operator">||</span> <span class="token operator">!</span>userToken<span class="token punctuation">.</span>name<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Forbbiden</span><span class="token punctuation">(</span>errMsg<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
      <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// 验证令牌是否合法, 其中userToken.name是令牌字符串, secretKey是用户私有key</span>
        <span class="token comment">// 如果验证通过, 则会返回我们给令牌传的值</span>
        <span class="token comment">// 这里decode需要用var关键词, 否则后续拿不到该值</span>
        <span class="token keyword">var</span> decode <span class="token operator">=</span> jwt<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span>
          userToken<span class="token punctuation">.</span>name<span class="token punctuation">,</span>
          global<span class="token punctuation">.</span>config<span class="token punctuation">.</span>security<span class="token punctuation">.</span>secretKey
        <span class="token punctuation">)</span>
      <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">.</span>name <span class="token operator">===</span> <span class="token string">'TokenExpiredError'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          errMsg <span class="token operator">=</span> <span class="token string">'令牌已过期'</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Forbbiden</span><span class="token punctuation">(</span>errMsg<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
      <span class="token comment">// 将验证后的值统一保存到ctx的auth属性中</span>
      ctx<span class="token punctuation">.</span>auth <span class="token operator">=</span> <span class="token punctuation">{</span>
        uid<span class="token operator">:</span> decode<span class="token punctuation">.</span>uid<span class="token punctuation">,</span>
        scope<span class="token operator">:</span> decode<span class="token punctuation">.</span>scope<span class="token punctuation">,</span>
      <span class="token punctuation">}</span>
      <span class="token comment">// 调用后续的中间件函数</span>
      <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> Auth <span class="token punctuation">}</span>
</code></pre></div><p>然后我们在<code>api</code>文件中新建<code>deploy.js</code>进行测试, 这里的 Auth 中间件需要放到我们自己使用的中间件前面, 先行进行调用, 如果不出问题, 则会在<code>ctx.auth</code>中找到传入值</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> Router <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'koa-router'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> Auth <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../../middlewares/auth'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Router</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  prefix<span class="token operator">:</span> <span class="token string">'/v1/deploy'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 将Auth中间件加入到router中</span>
router<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/getSetting'</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Auth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>m<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token comment">// 将保存的值显示出来</span>
  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> ctx<span class="token punctuation">.</span>auth
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><p>利用 postman 进行测试的时候, 需要选择<code>Authorization</code> 的<code>Basic Auth</code> 选项, 然后在<code>username</code>上传递之前生成的令牌, 如下图所示:<br> <img src="https://s1.ax1x.com/2020/04/27/JR8z7j.png" alt="postman测试basicAuth令牌"></p> <h3 id="前端获取令牌-token"><a href="#前端获取令牌-token" class="header-anchor">#</a> 前端获取令牌 token</h3> <p>在前端使用 httpAuth 的方式传递令牌, 需要进行 base64 加密, 先在前端安装 base64.js, <code>npm i js-base64 --save</code>, 示例代码如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 导入base64包</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span>Base64<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'js-base64'</span>

<span class="token keyword">const</span> token <span class="token operator">=</span> &quot;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9<span class="token punctuation">.</span>eyJ1aWQiOjEsInNjb3BlIjo4LCJpYXQiOjE1ODgwNTg4OTQsImV4cCI6MTU4ODA2MjQ5NH0<span class="token punctuation">.</span>kgU351Gvedl3gvPmvb4iW1UrsIKQUM9thWtbr7ltIsA
<span class="token function">axios</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  method<span class="token operator">:</span> <span class="token string">'POST'</span><span class="token punctuation">,</span>
  url<span class="token operator">:</span> <span class="token string">'/user/token'</span><span class="token punctuation">,</span>
  header<span class="token operator">:</span><span class="token punctuation">{</span>
    Authorization<span class="token operator">:</span><span class="token function">_encode</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// 进行base64加密</span>
<span class="token keyword">const</span> <span class="token function-variable function">_encode</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">token</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>
  <span class="token comment">// 一般加密的是username:password字符串形式,</span>
  <span class="token comment">// 但是因为这里只传递令牌, 所以把令牌放到username中, password部分为空</span>
  <span class="token keyword">const</span> base64 <span class="token operator">=</span> Base64<span class="token punctuation">.</span><span class="token function">encode</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span>
  <span class="token comment">// 标准的basicAuth需要在前面增加`Basic `, 所以这里需要加上</span>
  <span class="token keyword">return</span> <span class="token string">&quot;Basic &quot;</span> <span class="token operator">+</span> base64
<span class="token punctuation">}</span>
</code></pre></div><h3 id="权限设计"><a href="#权限设计" class="header-anchor">#</a> 权限设计</h3> <p>权限设计其实可以很简单, 就是用户给定一个权限值, 例如普通用户为 8, 管理员为 16, 然后给每个 api 一个权限数字, 如果用户权限大于 api 权限, 则可以查看, 否则没有权限, 这里设置不同用户的权限, 以及给 API 设置权限都在<code>middlewares/auth.js</code>类中, 其中加上注释的代码就是这次新增的.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> basicAuth <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'basic-auth'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> Forbbiden <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../core/http-exception'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> jwt <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'jsonwebtoken'</span><span class="token punctuation">)</span>

<span class="token keyword">class</span> <span class="token class-name">Auth</span> <span class="token punctuation">{</span>
  <span class="token comment">// 设置Auth的不同用户的常量权限, 以及传入api的权限等级</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">level</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>level <span class="token operator">=</span> level <span class="token operator">||</span> <span class="token number">1</span> <span class="token comment">// api的权限等级, 默认为1</span>
    Auth<span class="token punctuation">.</span><span class="token constant">USER</span> <span class="token operator">=</span> <span class="token number">8</span>
    Auth<span class="token punctuation">.</span><span class="token constant">ADMIN</span> <span class="token operator">=</span> <span class="token number">16</span>
    Auth<span class="token punctuation">.</span><span class="token constant">SUPER_ADMIN</span> <span class="token operator">=</span> <span class="token number">32</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">get</span> <span class="token function">m</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
      <span class="token keyword">const</span> userToken <span class="token operator">=</span> <span class="token function">basicAuth</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>req<span class="token punctuation">)</span>
      <span class="token keyword">let</span> errMsg <span class="token operator">=</span> <span class="token string">'token不合法'</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>userToken <span class="token operator">||</span> <span class="token operator">!</span>userToken<span class="token punctuation">.</span>name<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Forbbiden</span><span class="token punctuation">(</span>errMsg<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
      <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token keyword">var</span> decode <span class="token operator">=</span> jwt<span class="token punctuation">.</span><span class="token function">verify</span><span class="token punctuation">(</span>
          userToken<span class="token punctuation">.</span>name<span class="token punctuation">,</span>
          global<span class="token punctuation">.</span>config<span class="token punctuation">.</span>security<span class="token punctuation">.</span>secretKey
        <span class="token punctuation">)</span>
      <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>error<span class="token punctuation">.</span>name <span class="token operator">===</span> <span class="token string">'TokenExpiredError'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
          errMsg <span class="token operator">=</span> <span class="token string">'令牌已过期'</span>
        <span class="token punctuation">}</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Forbbiden</span><span class="token punctuation">(</span>errMsg<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
      <span class="token comment">// 进行权限管理</span>
      <span class="token comment">// 如果用户权限小于当前api的权限范围, 则抛出权限不足的错误</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span>decode<span class="token punctuation">.</span>scope <span class="token operator">&lt;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>level<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        errMsg <span class="token operator">=</span> <span class="token string">'权限不足'</span>
        <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Forbbiden</span><span class="token punctuation">(</span>errMsg<span class="token punctuation">)</span>
      <span class="token punctuation">}</span>
      ctx<span class="token punctuation">.</span>auth <span class="token operator">=</span> <span class="token punctuation">{</span>
        uid<span class="token operator">:</span> decode<span class="token punctuation">.</span>uid<span class="token punctuation">,</span>
        scope<span class="token operator">:</span> decode<span class="token punctuation">.</span>scope<span class="token punctuation">,</span>
      <span class="token punctuation">}</span>
      <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> Auth <span class="token punctuation">}</span>
</code></pre></div><p>使用过程中,首先颁布令牌的时候,对于普通用户就要赋予<code>Auth.USER</code>的权限, 在<code>api/token.js</code>中,验证身份后, 生成令牌就可以填写<code>Auth.USER</code>值.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token punctuation">{</span> Auth <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../../middlewares/auth'</span><span class="token punctuation">)</span>

<span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">emailLogin</span><span class="token punctuation">(</span><span class="token parameter">account<span class="token punctuation">,</span> secret</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> User<span class="token punctuation">.</span><span class="token function">verifyEmailPassword</span><span class="token punctuation">(</span>account<span class="token punctuation">,</span> secret<span class="token punctuation">)</span>
  <span class="token comment">// 获得用户后生成令牌</span>
  <span class="token keyword">return</span> <span class="token function">generateToken</span><span class="token punctuation">(</span>user<span class="token punctuation">.</span>id<span class="token punctuation">,</span> Auth<span class="token punctuation">.</span><span class="token constant">USER</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span>
</code></pre></div><p>用的时候也很简单, 在<code>deploy.js</code>中, 增加<code>Auth()</code>类实例化时传参即可.</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// Auth中可以传入api的权限等级, 等级越高, 所需的权限就越高</span>
router<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/getSetting'</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Auth</span><span class="token punctuation">(</span><span class="token number">12</span><span class="token punctuation">)</span><span class="token punctuation">.</span>m<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> ctx<span class="token punctuation">.</span>auth
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router
</code></pre></div><h3 id="业务逻辑编写位置"><a href="#业务逻辑编写位置" class="header-anchor">#</a> 业务逻辑编写位置</h3> <p>业务逻辑可以在 API 接口或者 Model 模型中: 如果业务<strong>很简单</strong>, 可以写到 api 中, 否则应该写到 model 层中. 如果业务很复杂, 则可以分为三层, 分别是 api(controller)/services/model, 其中 services 存放业务比较复杂的代码.</p> <p>node.js 提供了一个帮助工具<code>util</code>, <a href="https://nodejs.org/docs/latest-v12.x/api/util.html" target="_blank" rel="noopener noreferrer">util 文档<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>里面提供了很多帮助函数, 其中有本文格式化的函数, 还有类似于深比较<code>util.isDeepStrictEqual(val1, val2)</code>,<code>util.types</code>提供各种类型的内置对象的类型检查,字符编码<code>util.TextDecoder</code>和<code>util.TextEncoder</code>, <code>util.inspect()</code>方法返回<code>object</code>用于调试的字符串表示形式。</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> util <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'util'</span><span class="token punctuation">)</span>
<span class="token comment">// 文本格式化</span>
<span class="token keyword">const</span> url <span class="token operator">=</span> util<span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">'测试%s'</span><span class="token punctuation">,</span> id<span class="token punctuation">)</span>
</code></pre></div><p>但是如果跟数据交互相关, 代码应该写到<code>model</code>模型类中, 例如微信小程序提供了 openId, 我们要查询 openId 是否已经存在, 那么这个方法应该写到模型类<code>User</code>当中.</p> <h2 id="数据库-database-操作"><a href="#数据库-database-操作" class="header-anchor">#</a> 数据库(database)操作</h2> <h3 id="实体表和业务表概念"><a href="#实体表和业务表概念" class="header-anchor">#</a> 实体表和业务表概念</h3> <p>实体表是实体, 而业务表是虚表, 通过与实体的联系并附加一些信息, 从而方便业务操作.</p> <p>使用概括的单词来描述一些实体类, 这样可以方便文件命名, 例如<code>classic.js</code>用来包括<code>sentence</code>, <code>music</code>等, 这样 classic 可以作为基类.</p> <p>但是 javascript 中的 orm 框架中不能使用继承来实现.因此只能采用折中的办法进行设计, 例如<code>classic.js</code>写法如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token punctuation">{</span> Sequelize<span class="token punctuation">,</span> Model <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sequelize'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> sequelize <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../core/db'</span><span class="token punctuation">)</span>

<span class="token comment">// 由于sequelize不能采用类属性的方式定义字段</span>
<span class="token comment">// 所以这里单独声明一个classic字段对象</span>
<span class="token keyword">const</span> classicFields <span class="token operator">=</span> <span class="token punctuation">{</span>
  image<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">,</span>
  content<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">,</span>
  pubdate<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">DATEONLY</span><span class="token punctuation">,</span>
  favNums<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">INTEGER</span><span class="token punctuation">,</span>
  title<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span><span class="token punctuation">,</span>
  type<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">TINYINT</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span>
<span class="token comment">// 定义电影的模型类</span>
<span class="token keyword">class</span> <span class="token class-name">Movie</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token comment">// 电影模型类进行初始化, 第1个参数是classic模型的属性,使用classicFields</span>
<span class="token comment">// 第2个参数传递一些参数, 包括sequelize实例和表名</span>
Movie<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span>classicFields<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  sequelize<span class="token punctuation">,</span>
  tableName<span class="token operator">:</span> <span class="token string">'movie'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 与电影一样, 定义名句的模型类</span>
<span class="token keyword">class</span> <span class="token class-name">Sentence</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
<span class="token comment">// 初始化电影</span>
Sentence<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span>classicFields<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  sequelize<span class="token punctuation">,</span>
  tableName<span class="token operator">:</span> <span class="token string">'sentence'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 与电影一样, 定义音乐的模型类</span>
<span class="token keyword">class</span> <span class="token class-name">Music</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

<span class="token comment">// 需要注意的是, 音乐多了一个字段, 叫做musicUrl</span>
<span class="token comment">// 使用Object.assign在classicFields的基础上增加url字段</span>
<span class="token keyword">const</span> musicFields <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">assign</span><span class="token punctuation">(</span><span class="token punctuation">{</span> url<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">STRING</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> classicFields<span class="token punctuation">)</span>
Music<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span>musicFields<span class="token punctuation">,</span> <span class="token punctuation">{</span>
  sequelize<span class="token punctuation">,</span>
  tableName<span class="token operator">:</span> <span class="token string">'music'</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  Movie<span class="token punctuation">,</span>
  Sentence<span class="token punctuation">,</span>
  Music<span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre></div><p>而相关的业务表<code>flow.js</code>写法如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token punctuation">{</span> Sequelize<span class="token punctuation">,</span> Model <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sequelize'</span><span class="token punctuation">)</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> sequelize <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../../core/db'</span><span class="token punctuation">)</span>

<span class="token keyword">class</span> <span class="token class-name">Flow</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>

Flow<span class="token punctuation">.</span><span class="token function">init</span><span class="token punctuation">(</span>
  <span class="token punctuation">{</span>
    index<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">INTEGER</span><span class="token punctuation">,</span>
    artId<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">INTEGER</span><span class="token punctuation">,</span>
    <span class="token comment">// type是表示每天展现的形式</span>
    <span class="token comment">// 100表示电影,200表示句子,300表示音乐</span>
    type<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">INTEGER</span><span class="token punctuation">,</span>
    fav_num<span class="token operator">:</span> <span class="token punctuation">{</span>
      type<span class="token operator">:</span> Sequelize<span class="token punctuation">.</span><span class="token constant">INTEGER</span><span class="token punctuation">,</span>
      <span class="token comment">// 可以设置默认值</span>
      <span class="token keyword">default</span><span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">{</span>
    sequelize<span class="token punctuation">,</span>
    tableName<span class="token operator">:</span> <span class="token string">'flow'</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">)</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  Flow<span class="token punctuation">,</span>
<span class="token punctuation">}</span>
</code></pre></div><h3 id="数据排序-order-删除-destory-高级查找-find"><a href="#数据排序-order-删除-destory-高级查找-find" class="header-anchor">#</a> 数据排序(order), 删除(destory), 高级查找(find)</h3> <p>排序使用 order 属性进行排序操作:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// flow按照index进行倒序, 取第一个</span>
<span class="token keyword">const</span> flow <span class="token operator">=</span> Flow<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  order<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token string">'index'</span><span class="token punctuation">,</span> <span class="token string">'DESC'</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre></div><p>利用事务进行删除操作, 分为硬删除和软删除两种模式:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">await</span> flow<span class="token punctuation">.</span><span class="token function">destory</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token comment">// 表明是硬删除还是软删除, 如果true表示硬删除,</span>
  <span class="token comment">// false表示只是增加了一个delete_time, 数据还在</span>
  force<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
  <span class="token comment">// 传递的事务t</span>
  transaction<span class="token operator">:</span> t<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre></div><p>高级查找功能:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token punctuation">{</span> Op <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'sequelize'</span><span class="token punctuation">)</span>
<span class="token comment">// 找到所有年龄在16,17,18, 且type值不等于400的值, 并按照art_id进行分组</span>
Favor<span class="token punctuation">.</span><span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  where<span class="token operator">:</span> <span class="token punctuation">{</span>
    ages<span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token punctuation">[</span>Op<span class="token punctuation">.</span>in<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token number">16</span><span class="token punctuation">,</span> <span class="token number">17</span><span class="token punctuation">,</span> <span class="token number">18</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    type<span class="token operator">:</span> <span class="token punctuation">{</span>
      <span class="token comment">// [Op.not]会转成一个字符串, 也就是说a可以是表达式进行运算,[a]会变为一个字符串</span>
      <span class="token punctuation">[</span>Op<span class="token punctuation">.</span>not<span class="token punctuation">]</span><span class="token operator">:</span> <span class="token number">400</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token comment">// 分组情况</span>
  group<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'art_id'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token comment">// 返回的属性</span>
  attributes<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'art_id'</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>Sequelize<span class="token punctuation">.</span><span class="token function">fn</span><span class="token punctuation">(</span><span class="token string">'COUNT'</span><span class="token punctuation">,</span> <span class="token string">'*'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'count'</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre></div><h3 id="模型序列化"><a href="#模型序列化" class="header-anchor">#</a> 模型序列化</h3> <p>首先添加<code>增加电影</code>和<code>读取电影</code>的 api, 由于返回给用户的信息需要增加或者删除某些字段, 因此这里对返回属性进行了一定的修改</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 增加电影情况</span>
router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/movie/add'</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Auth</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span>m<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> v <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">MovieAddValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>
  <span class="token keyword">const</span> movie <span class="token operator">=</span> <span class="token punctuation">{</span>
    title<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.title'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    content<span class="token operator">:</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'body.content'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
  <span class="token keyword">await</span> Movie<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>movie<span class="token punctuation">)</span>
  <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Success</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>

<span class="token comment">// 返回指定id的电影</span>
router<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'/movie/:id'</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Auth</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">.</span>m<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> v <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">MovieGetValidator</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">validate</span><span class="token punctuation">(</span>ctx<span class="token punctuation">)</span>
  <span class="token keyword">const</span> queryId <span class="token operator">=</span> v<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'path.id'</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> movie <span class="token operator">=</span> <span class="token keyword">await</span> Movie<span class="token punctuation">.</span><span class="token function">getMovieById</span><span class="token punctuation">(</span>queryId<span class="token punctuation">)</span>
  <span class="token comment">// 在返回结果中增加某个属性, 需要加到dataValues中去</span>
  <span class="token comment">// movie.dataValues.addAttrib = '增加的属性'</span>
  <span class="token comment">// 但是不推荐这种写法, sequlize提供了增加属性的方法时</span>
  movie<span class="token punctuation">.</span><span class="token function">setDataValue</span><span class="token punctuation">(</span><span class="token string">'addAttrib'</span><span class="token punctuation">,</span> <span class="token string">'增加的属性'</span><span class="token punctuation">)</span>
  ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> movie
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre></div><p>通过在电影模型的序列化时指定导出的属性, 可以有效控制前端展现的情况:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 定义电影的模型类</span>
<span class="token keyword">class</span> <span class="token class-name">Movie</span> <span class="token keyword">extends</span> <span class="token class-name">Model</span> <span class="token punctuation">{</span>
  <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token function">getMovieById</span><span class="token punctuation">(</span><span class="token parameter">id</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> movie <span class="token operator">=</span> <span class="token keyword">await</span> Movie<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      where<span class="token operator">:</span> <span class="token punctuation">{</span> id <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>movie<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NotFound</span><span class="token punctuation">(</span><span class="token string">'未能找到指定id的电影'</span><span class="token punctuation">)</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> movie
  <span class="token punctuation">}</span>

  <span class="token comment">// 利用toJson方法, 返回指定数据</span>
  <span class="token comment">// 如果想排除某些数据, 可以先拷贝this.dataValue的所有值, 然后删除指定属性</span>
  <span class="token function">toJSON</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">{</span>
      title<span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getDataValue</span><span class="token punctuation">(</span><span class="token string">'title'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
      content<span class="token operator">:</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getDataValue</span><span class="token punctuation">(</span><span class="token string">'content'</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>因此, 可以在 Model 基类上定义 toJSON, 可以全局排除</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 导入lodash中的去除属性和浅拷贝方法</span>
<span class="token keyword">const</span> <span class="token punctuation">{</span> unset<span class="token punctuation">,</span> clone <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'lodash'</span><span class="token punctuation">)</span>

<span class="token comment">// 在Model基类上定义toJSON方法排除3个日期字段</span>
<span class="token class-name">Model</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">toJSON</span> <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> data <span class="token operator">=</span> <span class="token function">clone</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>dataValues<span class="token punctuation">)</span>
  <span class="token function">unset</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token string">'updated_at'</span><span class="token punctuation">)</span>
  <span class="token function">unset</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token string">'created_at'</span><span class="token punctuation">)</span>
  <span class="token function">unset</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> <span class="token string">'deleted_at'</span><span class="token punctuation">)</span>
  <span class="token comment">// 如果对象有exclude数组, 则在序列化的时候排除指定属性</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isArray</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>exclude<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>exclude<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token function">unset</span><span class="token punctuation">(</span>data<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>

  <span class="token keyword">return</span> data
<span class="token punctuation">}</span>
</code></pre></div><p>使用的时候也很简单,在 api 最终调用的时候, 对于模型返回的 sequelize 对象增加 exclude 数组, 即可排除指定属性, 例如<code>movie.exclude = ['pubdate']</code></p> <h3 id="数据库事务-transaction"><a href="#数据库事务-transaction" class="header-anchor">#</a> 数据库事务(Transaction)</h3> <p>默认情况下，Sequelize 不使用事务。但是，对于 Sequelize 的生产使用，应该将 Sequelize 配置为使用事务, <a href="https://sequelize.org/master/manual/transactions.html" target="_blank" rel="noopener noreferrer">示例说明文档<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>叙述的非常详细.<br>
Sequelize 支持两种使用事务的方式：</p> <ul><li>非托管事务：提交和回滚事务应由用户手动完成（通过调用适当的 Sequelize 方法）。</li> <li>托管事务：如果引发任何错误，Sequelize 将自动回滚事务，否则将提交事务。</li></ul> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">return</span> sequelize<span class="token punctuation">.</span><span class="token function">transaction</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">t</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">await</span> Favor<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token punctuation">{</span> art_id<span class="token punctuation">,</span> type<span class="token punctuation">,</span> uid <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> transaction<span class="token operator">:</span> t <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token keyword">const</span> art <span class="token operator">=</span> <span class="token keyword">await</span> Art<span class="token punctuation">.</span><span class="token function">getData</span><span class="token punctuation">(</span>art_id<span class="token punctuation">,</span> type<span class="token punctuation">)</span>
  <span class="token comment">// 利用increment方法可以给某个值增加一个数, 后面传的by后面就是增加的值</span>
  <span class="token comment">// decrement表示减去一个数</span>
  <span class="token keyword">await</span> art<span class="token punctuation">.</span><span class="token function">increment</span><span class="token punctuation">(</span><span class="token string">'fav_num'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> by<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> transaction<span class="token operator">:</span> t <span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre></div><h2 id="其他需要注意的问题"><a href="#其他需要注意的问题" class="header-anchor">#</a> 其他需要注意的问题</h2> <h3 id="常见问题"><a href="#常见问题" class="header-anchor">#</a> 常见问题</h3> <ol><li><code>axios</code>库对于中文不会自动进行编码, 因此需要用 node.js 内置的<code>encodeURI()</code>将可能为中文的字段进行转码.</li> <li>mysql 对于形如<code>%sql%</code>的模糊搜索, 会执行全表扫描, 不走索引, 所以速度比较慢.</li> <li>在 Model 上不要定义构造函数, 否则会出错.</li></ol> <h3 id="增加跨域中间件-cross-domain"><a href="#增加跨域中间件-cross-domain" class="header-anchor">#</a> 增加跨域中间件(cross-domain)</h3> <p>在<code>middlewares</code>文件夹下新增<code>cross-domain.js</code>文件, 主要处理与前端项目的跨域请求问题, 前端项目的地址写到全局配置<code>config.js</code>中, 增加一行<code>frontServer: 'http://localhost:3000'</code>, <code>cross-domain.js</code>主要内容如下:</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> <span class="token function-variable function">crossDomain</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token parameter">ctx<span class="token punctuation">,</span> next</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> frontServer <span class="token operator">=</span> global<span class="token punctuation">.</span>config<span class="token punctuation">.</span>frontServer
  ctx<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'Access-Control-Allow-Origin'</span><span class="token punctuation">,</span> frontServer<span class="token punctuation">)</span> <span class="token comment">// 配置跨域范围</span>
  ctx<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>
    <span class="token string">'Access-Control-Allow-Headers'</span><span class="token punctuation">,</span>
    <span class="token string">'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild'</span>
  <span class="token punctuation">)</span>
  ctx<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'Access-Control-Allow-Methods'</span><span class="token punctuation">,</span> <span class="token string">'PUT, POST, GET, DELETE, OPTIONS'</span><span class="token punctuation">)</span>
  <span class="token comment">// 保证OPTIONS能返回200</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>ctx<span class="token punctuation">.</span>method <span class="token operator">==</span> <span class="token string">'OPTIONS'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token number">200</span>
  <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
    <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

module<span class="token punctuation">.</span>exports <span class="token operator">=</span> crossDomain
</code></pre></div><p>然后在 app.js 中增加跨域中间件</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> crossDomain <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./middlewares/cross-domain'</span><span class="token punctuation">)</span>

<span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Koa</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>catchError<span class="token punctuation">)</span>
<span class="token comment">// 配置跨域设置</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span>crossDomain<span class="token punctuation">)</span>
app<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token function">parser</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
InitManager<span class="token punctuation">.</span><span class="token function">initCore</span><span class="token punctuation">(</span>app<span class="token punctuation">)</span>

app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">8080</span><span class="token punctuation">)</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Server Start in port 8080!'</span><span class="token punctuation">)</span>
</code></pre></div></div> <footer class="page-edit"><!----> <div class="last-updated"><span class="prefix">更新于:</span> <span class="time">9/11/2020, 8:28:39 PM</span></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
      ←
      <a href="/back-end/python-projects.html" class="prev">
        小python项目
      </a></span> <span class="next"><a href="/back-end/numpy.html">
        从python到Numpy
      </a>
      →
    </span></p></div> </main></div><div class="global-ui"><!----></div></div>
    <script src="/assets/js/app.9e067b79.js" defer></script><script src="/assets/js/2.983eb755.js" defer></script><script src="/assets/js/11.31600f80.js" defer></script>
  </body>
</html>
